高阶函数是指满足下列条件之一的函数:
函数可以作为参数进行传递函数可以作为返回值进行输出 
JavaScript 语言中的函数显然满足高阶函数的条件,在实际开发中,无论是将函数当做参数传递,还是让函数的执行结果返回给另外一个函数,这两种情形都有很多应用场景。
函数作为参数传递 
把函数当做参数进行传递,这代表我们可以抽离出一部分容易变化的业务逻辑,把这部分业务逻辑放在函数参数中,这样一来就可以分离业务代码中变化和不变的部分。
在 ajax 异步请求的应用中,回调函数的使用非常频繁。当我们想在 ajax 请求返回之后做些事情,但又并不知道请求返回的确切时间时,最常见的方案是把 callback 函数当做参数传入发起的 ajax 请求的方法中,待请求完成之后执行 callback 函数:
1 2 3 4 5 6 7 var  getUserInfo = function  (userId, callback ) {    $.ajax ("http://webape.net/getUserInfo?"  + userId, function  (data ) {         if  (typeof  callback === "function" ) {             callback (data);         }     }); }; 
 
再来看一个例子,假设有这样一个需求,需要创建 100 个 div 元素,同时把他们隐藏起来,那么可以看到下面这种实现:
1 2 3 4 5 6 7 8 9 var  appendDiv = function  ( ) {    for  (var  i = 0 ; i < 100 ; i++) {         var  div = document .createElement ("div" );         div.innerHTML  = i;         document .body .appendChild (div);         div.style .display  = "none" ;     } }; appendDiv ();
 
把 div.style.display = 'none'的逻辑硬编码在 appendDiv 里显然是不合理的,appendDiv 未免有点个性化,成为了一个难以复用的函数,并不是每个人创建了节点之后就希望它们立刻被隐藏。于是我们把这段代码抽离出来,用回调函数的形式传入 appendDiv 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 var  appendDiv = function  (callback ) {    for  (var  i = 0 ; i < 100 ; i++) {         var  div = document .createElement ("div" );         div.innerHTML  = i;         document .body .appendChild (div);         if  (typeof  callback === "function" ) {             callback (div);         }     } }; appendDiv (function  (node ) {    node.style .display  = "none" ; }); 
 
Array.prototype.sort 接受一个函数作为参数,这个函数里面封装了数组元素的排序顺序。我们的目的是对数组进行排序,这是不变的部分;但用什么规则去排序这是可变的部分。
1 2 3 4 [3 , 9 , 8 , 5 ].sort (function  (a, b ) {     return  a - b; }); 
 
函数作为返回值输出 
判断一个数据是否是数组,在以往的实现当中,可以基于鸭子类型的概念来判断,比如这个数据有没有 length 属性,有没有 sort 方法等。但更好的方法是用 Object.prototype.toString 来计算。根据 Object.prototype.toString.call( [1, 2, 3] ) 总是返回 ’[object Array]’ ,Object.prototype.toStrng.call( ‘str’ ) 也总是返回 ’[object Array]’ 得出,它总是会返回一个类似结构的字符串。于是用循环语句来批量注册类型判断的函数:
1 2 3 4 5 6 7 8 9 10 11 12 var  Type  = {};for  (var  i = 0 , type; (type = ["String" , "Array" , "Number" ][i++]); ) {    (function  (type ) {         Type ["is"  + type] = function  (obj ) {             return  (                 Object .prototype  .toString .call (obj) === "[object "  + type + "]"              );         };     })(type); } Type .isArray ([]); Type .isString ("" ); 
 
下面是一个单例模式的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 var  getSingle = function  (fn ) {    var  ret;     return  function  ( ) {         return  ret || (ret = fn.apply (this , arguments ));     }; }; var  getScript = getSingle (function  ( ) {    return  document .createElement ("script" ); }); var  script1 = getScript ();var  script2 = getScript ();console .log (script1 === script2); 
 
高阶函数实现 AOP 
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来后,再通过“动态植入”的方式掺入业务逻辑模块中。这样做的好处是首先保证了业务逻辑模块的纯洁和高内聚性,其实是可以很方便的复用这些日志统计等功能模块。在 JavaScript 中,我们可以通过Function.prototype 来实现 AOP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Function .prototype  .before  = function  (beforeFn ) {    var  _self = this ;     return  function  ( ) {         beforeFn.apply (this , arguments );         return  _self.apply (this , arguments );     }; }; Function .prototype  .after  = function  (afterFn ) {    var  _self = this ;     return  function  ( ) {         var  ret = _self.apply (this , arguments );         afterFn.apply (this , arguments );         return  ret;     }; }; var  func = function  ( ) {    console .log (2 ); }; func = func     .before (function  ( ) {         console .log (1 );     })     .after (function  ( ) {         console .log (3 );     }); func (); 
 
高阶函数的其他应用 
currying 的概念最早由俄国数学家 Moses Schoofinkel 发明,而后由著名的数理逻辑学家 Haskell Curry 将其丰富和发展,currying 由此得名。
currying 又称部分求值。这里我们讨论的是函数柯里化( function currying )。一个柯里化函数首先会接受一些参数,接受了这些参数会后,该函数并不会立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来。待到函数被真正求值的时候,之前传入的所有参数都会被一次性用于求值。来看一个例子:假设我们要编写一个计算每月开销的函数。在每天结束之前,我们都要记录几天花掉了多少钱。
1 2 3 4 5 6 7 8 9 10 11 var  monthCost = 0 ;var  cost = function  (money ) {    monthCost += money; }; cost (100 );第一天; cost (200 );第二天; cost (300 );第三天; console .log (monthCost); 
 
通过这段代码,我们可以看到每天都花了多少钱,但是如果我们只想知道每个月的消费如何的话,那就没必要计算每天的花费了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var  cost = (function  ( ) {    var  args = [];     return  function  ( ) {         if  (arguments .length  === 0 ) {             var  money = 0 ;             for  (var  i = 0 , l = args.length ; i < l; i++) {                 money += args[i];             }             return  money;         } else  {             [].push .apply (args, arguments );         }     }; })(); cost (100 );cost (200 );cost (300 );console .log (cost ()); 
 
接下来编写一个通用的柯里化函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var  currying = function  (fn ) {    var  args = [];     return  function  ( ) {         if  (arguments .length  === 0 ) {             return  fn.apply (this , args);         } else  {             [].push .apply (args, arguments );             return  arguments .callee ;         }     }; }; var  cost = (function  ( ) {    var  money = 0 ;     return  function  ( ) {         for  (var  i = 0 , l = arguments .length ; i < l; i++) {             money += arguments [i];         }         return  money;     }; })(); var  cost = currying (cost);cost (100 );cost (200 );cost (300 );console .log (cost ()); 
 
uncurrying 是反柯里化,大概意思是扩大函数的应用范围,将本来只有特定对象才能使用的方法,扩展到更多的对象。比如我们常常让类数组对象去借用 Array.prototype 的方法:
1 2 3 4 (function  ( ) {     Array .prototype  .push .call (arguments , 4 );     console .log (arguments );  })(1 , 2 , 3 ); 
 
uncurrying第一种实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Function .prototype  .uncurrying  = function  ( ) {    var  self = this ;     return  function  ( ) {         var  obj = Array .prototype  .shift .call (arguments );         return  self.apply (obj, arguments );     }; }; for  (var  i = 0 , fn, ary = ["push" , "shift" , "forEach" ]; (fn = ary[i++]); ) {    Array [fn] = Array .prototype  [fn].uncurrying (); } var  obj = {    length : 3 ,     0 : 1 ,     1 : 2 ,     2 : 3 , }; Array .push (obj, 4 );console .log (obj.length ); var  first = Array .shift (obj);console .log (first); console .log (obj); Array .forEach (obj, function  (i, n ) {    console .log (n);  }); 
 
uncurrying的第二种实现:
1 2 3 4 5 6 Function .prototype  .uncurrying  = function  ( ) {    var  self = this ;     return  function  ( ) {         return  Function .prototype  .call .apply (self, arguments );     }; }; 
 
在 JavaScript 中,大部分的函数都是由用户主动调动触发的。但是也存在少数情况,这些情况下函数的触发并不是又用户直接控制的。这个时候函数就有可能被频繁地调用,而造成大的性能问题。以下几个场景函数将被频繁调用:给 window 绑定了onresize 事件的时候,如果存在 DOM 相关的操作,那这个时候是非常耗性能的,严重的时候浏览器可能会卡顿;mousemove 事件,如果给某个元素绑定了拖拽事件,那么该函数也会被频繁的触发;在比如上传一个文件的时候,可能需要频繁的通知进度信息等。
函数节流就是为了避免函数被频繁地调用而存在的一种解决方案,从而优化性能。通常是用 setTimeout 来实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var  throttle = function  (fn, interval ) {    var  _self = fn,         timer,         firstTime = true ;     return  function  ( ) {         var  args = arguments ,             _me = this ;         if  (firstTime) {             _self.apply (_me, args);             return  (firstTime = false );         }         if  (timer) {             return  false ;         }         timer = setTimeout (function  ( ) {             clearTimeout (timer);             timer = null ;             _self.apply (_me, args);         }, interval || 500 );     }; }; window .onresize  = throttle (function  ( ) {    console .log (1 ); }, 500 ); 
 
上面我们介绍了一种解决函数被频繁调用的方法。但是有时候,用户确实有这种需求,比如需要在短时间内把 1000 个 qq 好友渲染到列表上,这个时候就可能会很卡。但是如果把 1000ms 创建 1000 个节点,改成每 200ms 创建 8 个节点。这个时候就避免这种问题。分时函数接受 3 个参数:第一个是创建节点的时候需要用到的数据,第二个是封装了创建节点的函数,第三个是每一批创建的节点数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var  timeChunk = function  (ary, fn, count ) {    var  obj, t, start;     start = function  ( ) {         for  (var  i = 0 ; i < Math .min (count || 1 , ary.length ); i++) {             var  obj = ary.shift ();             fn (obj);         }     };     return  function  ( ) {         t = setInterval (function  ( ) {             if  (ary.length  === 0 ) {                                  clearInterval (t);             }             start ();         }, 200 );     }; }; 
 
分时函数有了,现在我们来测试一下。假设有 1000 个好友,利用 timeChunk 函数,每批往页面上渲染 8 个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var  ary = [];for  (var  i = 0 ; i <= 1000 ; i++) {    ary.push (i); } var  renderFriendList = timeChunk (    ary,     function  (n ) {         var  div = document .createElement ("div" );         div.innerHTML  = n;         document .body .appendChild (div);     },     8  ); renderFriendList ();
 
在 web 开发的过程中,因为浏览器之间的实现差异,一些嗅探工作总是避免不了的。比如我们需要一个能在各个浏览器都能通用的事件绑定函数 addEvent,常见的写法如下:
1 2 3 4 5 6 7 8 var  addEvent = function  (elem, type, handler ) {    if  (window .addEventListener ) {         return  elem.addEventListener (type, handler, false );     }     if  (window .attachEvent ) {         return  elem.attachEvent ("on"  + type, handler);     } }; 
 
这种写法的缺点是每次调用函数都必须执行里面的 if 判断,虽然开销不大,但是有办法能避免这种操作:
1 2 3 4 5 6 7 8 9 10 11 12 var  addEvent = (function  ( ) {    if  (window .addEventListener ) {         return  function  (elem, type, handler ) {             elem.addEventListener (type, handler, false );         };     }     if  (window .attachEvent ) {         return  function  (elem, type, handler ) {             elem.attachEvent ("on"  + type, handler);         };     } })(); 
 
把嗅探的操作提前到代码加载之前,在代码加载的时候就即可进行一次判断,以便让 addEvent 返回一个正确的事件绑定函数。但是这种写法还是存在缺点的,如果我们从头到尾都不需要进行事件绑定,那么前面那次的嗅探动作就显得多余了。第三种方案是惰性载入函数方案,第一次进入 addEvent 函数的时候会重写事件绑定函数,在下次进去的时候就会直接执行事件绑定了。
1 2 3 4 5 6 7 8 9 10 11 12 13 var  addEvent = function  (elem, type, handler ) {    if  (window .addEventListener ) {         addEvent = function  (elem, type, handler ) {             elem.addEventListener (type, handler, false );         };     }     if  (window .attachEvent ) {         addEvent = function  (elem, type, handler ) {             elem.attachEvent ("on"  + type, handler);         };     }     addEvent (elem, type, handler); };