[读书笔记]高阶函数

1. 什么是高阶函数

  • 函数可以作为参数被传递;
  • 函数可以作为返回值输出。

2. 高阶函数的示例

2.1 函数作为参数传递

(1) 回调函数(callback)

回调函数类似于C#中的委托,在异步操作中应用较为广泛。如jQuery中的ajax:

 1 var getOrderInfo = function (callback) {
 2     $.ajax({
 3         type: 'POST',
 4         url: '/URL',
 5         data: "name=John&location=Boston",
 6         dataType: "json",
 7     }).then(function (data) {
 8         if (typeof callback === 'function') {
 9             // render 
10             callback(data);
11         }
12     }, function () {
13         console.error('error');
14     });
15 };

(2) Array.prototype.sort

将排序规则作为回调函数传给Array.prototype.sort方法来得到想要的排序结果:

 1 var s1 = [
 2     { name: '李雷', age: 12 },
 3     { name: '陈梅梅', age: 11 },
 4     { name: '露西', age: 11 },
 5     { name: '大卫', age: 13 }
 6 ],
 7 s2 = [].concat(s1),
 8 ageAsc = function (a, b) {
 9     return a.age - b.age;
10 },
11 ageDesc = function (a, b) {
12     return b.age - a.age;
13 };
14 // 年龄从小到大
15 s1.sort(ageAsc);
16 console.dir(s1);
17 // 年龄大到小
18 s2.sort(ageDesc);
19 console.info(s2);

2.2 函数作为返回值输出

(1) 判断数据类型

 1 var isType = function( type ){
 2     return function( obj ){
 3         return Object.prototype.toString.call( obj ) === '[object '+ type +']';
 4     }
 5 },
 6 isString = isType( 'String' ),
 7 isArray = isType( 'Array' ),
 8 isNumber = isType('Number');
 9 console.log(isString); // 输出isString
10 /*
11 function( obj ){
12     return Object.prototype.toString.call( obj ) === '[object '+ type +']';
13 }
14 */

(2) 获取单例

 1 var getSingle = function (fn) {
 2     var ret;
 3     return function () {
 4         return ret || (ret = fn.apply(this, arguments));
 5     };
 6 },
 7 getScript = getSingle(function () {
 8     return document.createElement('script');
 9 });
10 
11 var script1 = getScript(), // 第一次调用时将执行 (ret = fn.apply(this, arguments)
12     script2 = getScript(); // 第二次调用时直接返回 ret
13 console.log('script1 === script2 is ', script1 === script2); // 输出:true

3. 高阶函数的应用

3.1 高阶函数实现AOP

提起AOP(面向切面编程),可能会想起Spring MVC 中的AOP。实现如下:

 1 Function.prototype.before = function (beforefn) {
 2     var that = this; // 保存原函数的引用
 3     console.log(that); // that指向print2()
 4     // 返回包含了原函数和新函数的"代理"函数
 5     // return#1
 6     return function () {
 7         beforefn.apply(this, arguments); // 执行新函数,修正this
 8         return that.apply(this, arguments); // 执行原函数
 9     };
10 };
11 
12 Function.prototype.after = function (afterfn) {
13     var that = this; // 保存原函数的引用
14     console.log(that); // that 指向return#1
15     // return#2
16     return function () {
17         var ret = that.apply(this, arguments); // 执行原函数,并保存原函数的执行结果
18         afterfn.apply(this, arguments); // 执行新函数
19         return ret; // 在新函数执行完成之后返回原函数的执行结果
20     };
21 };
22 
23 var print1 = function () {
24     console.log(1);
25 },
26 print2 = function () {
27     console.log(2);
28 },
29 print3 = function () {
30     console.log(3);
31 };
32 
33 print2 = print2.before(print1).after(print3);
34 print2();

3.2 柯里化(currying)

currying 又称部分求值。一个currying 的函数首先会接受一些参数,接受了这些参数之后,
该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保
存起来。待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

 1 var currying = function (fn) {
 2     var args = [];
 3     return function () {
 4         if (arguments.length === 0) {
 5             console.log(this);
 6             return fn.apply(this, args);
 7         } else {
 8             console.log(arguments,args);
 9             [].push.apply(args, arguments);
10             // arguments.callee, Returns the Function object being executed
11             return arguments.callee; 
12         }
13     }
14 },
15 cost = (function () {
16     var money = 0;
17     return function () {
18         for (var i = 0, l = arguments.length; i < l; i++) {
19             money += arguments[i];
20         }
21         return money;
22     }
23 })();
24 
25 var cost = currying(cost); // 转化成currying 函数
26 cost(100); // 未真正求值
27 cost(200); // 未真正求值
28 cost(300); // 未真正求值
29 console.log(cost()); // 求值并输出:600

3.3 反柯里化(uncurrying)

 1         Function.prototype.uncurrying = function () {
 2             var self = this;
 3             return function () {
 4                 return Function.prototype.call.apply(self, arguments);
 5             }
 6         };
 7 
 8         var push = Array.prototype.push.uncurrying();
 9 
10         var obj = {
11             name: 'wills',
12             age: 26
13         };
14         push(obj, 'JavaScript programer');
15 
16         console.log(obj); // Object {0: "JavaScript programer", name: "wills", age: 27, length: 1}

3.4 函数节流

函数节流目的即降低函数被频繁调用的频率。代码实现如下:

 1 // 函数节流
 2 var throttle = function (fn, interval) {
 3     var __self = fn, // 保存需要被延迟执行的函数引用
 4     timer, // 定时器
 5     firstTime = true; // 是否是第一次调用
 6 
 7     return function () {
 8         var args = arguments,
 9             that = this;
10 
11         // 如果是第一次调用,不需延迟执行
12         if (firstTime) {
13             __self.apply(that, args);
14             return firstTime = false;
15         }
16 
17         if (timer) { // 如果定时器还在,说明前一次延迟执行还没有
18             return false;
19         }
20 
21         timer = setTimeout(function () { // 延迟一段时间执行
22             clearTimeout(timer);
23             timer = null;
24             __self.apply(that, args);
25         }, interval || 500);
26     };
27 };
28 
29 var resizeThrottle = throttle(function () {
30     console.log(1);
31 }, 500),
32 resize = function () {
33     console.log(2);
34 };
35 window.onresize = function () {
36     resizeThrottle();
37     resize(2);
38 };

3.5 分时函数

 分时函数与函数节流相似,使用场景一般为主动调用。

 1 var ary = [];
 2 for (var i = 1; i <= 1000; i++) {
 3     ary.push(i); // 假设ary 装载了1000 个好友的数据
 4 };
 5 var renderFriendList = function (data) {
 6     for (var i = 0, l = data.length; i < l; i++) {
 7         var div = document.createElement('div');
 8         div.innerHTML = i;
 9         document.body.appendChild(div);
10     }
11 };
12 renderFriendList(ary);
未使用分时函数优化前

使用分时函数之后:

var timeChunk = function (ary, fn, count) {
    /// <summary>
    /// 分时函数
    /// </summary>
    /// <param name="ary" type="Array">数据(对象)数组</param>
    /// <param name="fn" type="Function">函数</param>
    /// <param name="count" type="Number">每次执行的数组长度</param>
    var obj,
        t,
        len = ary.length,
        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) { // 如果全部节点都已经被创建好
                return clearInterval(t);
            }
            start();
        }, 200); // 分批执行的时间间隔,也可以用参数的形式传入
    };
};

var ary = [];
for (var i = 1; 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();

3.6 惰性加载函数

 1         var addEvent = function (elem, type, handler) {
 2             console.log('addEvent');
 3             if (window.addEventListener) {
 4                 console.log('addEventListener');
 5                 addEvent = function (elem, type, handler) {
 6                     elem.addEventListener(type, handler, false);
 7                 }
 8             } else if (window.attachEvent) {
 9                 console.log('attachEvent');
10                 addEvent = function (elem, type, handler) {
11                     elem.attachEvent('on' + type, handler);
12                 }
13             }
14             addEvent(elem, type, handler);
15         };
16 
17         var div = document.getElementById('div1');
18         addEvent(div, 'click', function () {
19             alert(1);
20         });
21         addEvent(div, 'click', function () {
22             alert(2);
23         });
原文地址:https://www.cnblogs.com/January/p/5430764.html