# 函数式编程离我们有多远？

## 函数式编程究竟是什么

function add(x, y){     return x + y; }  function mul(x, y){     return x * y; }  function concat(arr1, arr2){     return arr1.concat(arr2); }  console.log(add(1, add(2, 3)),     //6     mul(1, mul(2, mul(3, 4))),        //24     concat([1, 2], concat([3, 4], [5, 6]))); //[1,2,3,4,5,6]

#### 版本1

function add(...args){     return args.reduce((x, y) => x + y); }  function mul(...args){     return args.reduce((x, y) => x * y); }  function concat(...args){     return args.reduce((arr1, arr2) => arr1.concat(arr2)); }  console.log(add(1, 2, 3),     //6     mul(1, 2, 3, 4),        //24     concat([1, 2], [3, 4], [5, 6])); //[1,2,3,4,5,6]

#### 版本二

function reduce(fn, ...args){     return args.reduce(fn); }  function add(x, y){     return x + y; }  function mul(x, y){     return x * y; }  function concat(arr1, arr2){     return arr1.concat(arr2); }  console.log(reduce(add, 1, 2, 3),     //6     reduce(mul, 1, 2, 3, 4),        //24     reduce(concat, [1, 2], [3, 4], [5, 6])); //[1,2,3,4,5,6]

#### 版本三

function reduce(fn, ...args){     return args.reduce(fn); }  function add(x, y){     return x + y; }  function mul(x, y){     return x * y; }  function concat(arr1, arr2){     return arr1.concat(arr2); }  add = reduce.bind(null, add); mul = reduce.bind(null, mul); concat = reduce.bind(null, concat);  console.log(add(1, 2, 3),     //6     mul(1, 2, 3, 4),        //24     concat([1, 2], [3, 4], [5, 6])); //[1,2,3,4,5,6]

#### 版本四

function reduce(fn){     return function(...args){         return args.reduce(fn.bind(this));     } }  function add(x, y){     return x + y; }  function mul(x, y){     return x * y; }  function concat(arr1, arr2){     return arr1.concat(arr2); }  add = reduce(add); mul = reduce(mul); concat = reduce(concat);  console.log(add(1, 2, 3),     //6     mul(1, 2, 3, 4),        //24     concat([1, 2], [3, 4], [5, 6])); //[1,2,3,4,5,6]

function reduce(fn, async){   if(async){     return function(...args){       return args.reduce((a, b)=>{         return Promise.resolve(a).then((v)=>fn.call(this, v, b));       });     }   }else{     return function(...args){       return args.reduce(fn.bind(this));     }   } }  function add(x, y){   return x + y; }  function mul(x, y){   return x * y; }  function concat(arr1, arr2){   return arr1.concat(arr2); }  function asyncAdd(x, y){   return new Promise((resolve, reject)=>{     setTimeout(()=> resolve(x+y), 100);    }); }  add = reduce(add); mul = reduce(mul); concat = reduce(concat);  console.log(add(1, 2, 3),   //6   mul(1, 2, 3, 4),    //24   concat([1, 2], [3, 4], [5, 6])); //[1,2,3,4,5,6]  asyncAdd = reduce(asyncAdd, true); asyncAdd(1, 2, 3).then((v)=>console.log(v));

## 过程抽象的具体应用

### 函数调用的频度控制

#### throttle

throttle 可以限制函数调用的频率，常用来防止按钮被重复点击。

//限制button在500ms内只能被点击一次 \$("#btn").click(throttle(function(evt){     do sth. }, 500);

throttle 的简单实现

function throttle(fn, wait){     var timer;     return function(...args){         if(!timer){             timer = setTimeout(()=>timer=null, wait);             return fn.apply(this, args);         }     } }  //按钮每500ms一次点击有效 btn.onclick = throttle(function(){     console.log("button clicked"); }, 500);

#### debounce

searchBox.addEventListener("input", debounce(function(){     loadSuggestion(); }, 100));

debouce 的简单实现：

function debounce(fn, delay){     var timer = null;     return function(...args){         clearTimeout(timer);         timer = setTimeout(() => fn.apply(this, args), delay);     } }  window.addEventListener("scroll", debounce(function(){     console.log("scrolled"); }, 500));

### DOM的批量操作

jQuery 的语法糖特点包括批量操作与链式调用。我们可以看一下，实际上用函数式的过程抽象思想很容易实现类似的“语法糖”。我们先考虑如何支持函数的“批量操作”。

function multicast(fn){   return function(list, ...args){       if(Array.isArray(list)){       return list.map((item)=>fn.apply(this, [item,...args]));     }else{       return fn.apply(this, [list,...args]);     }   } }  function add(x, y){     return x + y; }  add = multicast(add);  console.log(add([1,2,3], 4)); //5,6,7

<ul>   <li>1</li>   <li>2</li>   <li>3</li>   <li>4</li>   <li>5</li>   <li>6</li>   <li>7</li> </ul>
function multicast(fn){   return function(list, ...args){       if(Array.isArray(list)){       return list.map((item)=>fn.apply(this, [item,...args]));     }else{       return fn.apply(this, [list,...args]);     }   } }  function setColor(el, color){   return el.style.color = color; }  setColor = multicast(setColor);  var list = document.querySelectorAll("li:nth-child(2n+1)"); setColor(Array.from(list), "red");

### Wrapper and decorators

function multicast(fn){   return function(list, ...args){       if(Array.isArray(list)){       return list.map((item)=>fn.apply(this, [item,...args]));     }else{       return fn.apply(this, [list,...args]);     }   } }  function wrap(fn, before, after){   return function(...args){     if(before){         args = before.apply(this, args);     }     let ret = fn.apply(this, args);     if(after){         ret = after.call(this, [ret, ...args]);     }     return ret;   } }  function setColor(el, color){   return el.style.color = color; }  function setFontSize(el, size){   return el.style.fontSize = size; }  function cast(fn){   return wrap(multicast(fn), (...args)=>{       if(typeof args[0] === "string"){           args[0] = Array.from(document.querySelectorAll(args[0]));       }       return args;   }); }  [setColor, setFontSize] = multicast(cast)([setColor, setFontSize]);  setColor("li:nth-child(2n+1)", "red");  setFontSize("li:nth-child(2n+1)", "32px");

function zip(props){   function Class(...args){     this.__args = args;   }   let keys = Object.keys(props);   keys.forEach((key) => {       Class.prototype[key] = function(...args){       return props[key].apply(this, [...this.__args, ...args]);     };   });   return (...args)=>new Class(...args); }  function add(x, y){   return x + y; }  function sub(x, y){   return x - y; }  let N = zip({add, sub});  console.log(N(9).add(8)); //17  console.log(N(3).sub(5)); //-2

function zip(props){   function Class(...args){     this.__args = args;   }   let keys = Object.keys(props);   keys.forEach((key) => {       Class.prototype[key] = function(...args){       return props[key].apply(this, [...this.__args, ...args]);     };   });   return (...args)=>new Class(...args); }  function multicast(fn){   return function(list, ...args){       if(Array.isArray(list)){       return list.map((item)=>fn.apply(this, [item,...args]));     }else{       return fn.apply(this, [list,...args]);     }   } }  function wrap(fn, before, after){   return function(...args){     if(before){         args = before.apply(this, args);     }     let ret = fn.apply(this, args);     if(after){         ret = after.apply(this, [ret, ...args]);     }     return ret;   } }  function setColor(el, color){   return el.style.color = color; }  function setFontSize(el, size){   return el.style.fontSize = size; }  function cast(fn){   return wrap(multicast(fn), (...args)=>{       if(typeof args[0] === "string"){           args[0] = Array.from(document.querySelectorAll(args[0]));       }       return args;   }, (ret,...args)=>\$(args[0])); }  [setColor, setFontSize] = multicast(cast)([setColor, setFontSize]);  let \$ = zip({setColor, setFontSize});  \$("li:nth-child(2n+1)").setColor("red").setFontSize("32px");

• 设计高阶函数：操作函数的函数，例如例子中的 multicast、wrap、cast、zip
• 高阶函数之间可以组合调用，例如 cast 调用 wrap， wrap 调用 multicast，cast 后的函数再被 zip 调用。组合调用可以给函数扩展出复杂的功能。

### 防御性编程

function wrap(fn, before, after){   return function(...args){     if(before){         args = before.apply(this, args);     }     let ret = fn.apply(this, args);     if(after){         ret = after.call(this, [ret, ...args]);     }     return ret;   } }  Object.defineProperty(window, "ERROR_IF_MISSING", {   get: function(){     throw new TypeError("missing parameter")   },   writeable: false });   function add(x, y){     return x + y; }  var add = wrap(add,            (x = ERROR_IF_MISSING, y = ERROR_IF_MISSING)=>[x, y]);  //Uncaught TypeError: missing parameter console.log(add());