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 });