闭包和高阶函数学习笔记
一、闭包
闭包的形成与变量的作用域以及变量的生存周期密切相关。
1.1 变量的作用域
变量的作用域:指变量的有效范围。

这里还涉及到“变量声明方式”、“变量提升”的影响。
1.2 变量的生存周期
- 全局变量:生存周期是永久的,除非主动销毁这个全局变量。
- 函数作用域内的局部变量:当退出函数时,如果这些局部变量没有被其它函数引用,那这些局部变量会跟随函数调用的结束而被销毁。
- 块状作用域中的变量:变量只在代码块内起作用。
二、闭包的应用场景
2.1 封装变量
把不需要暴露在全局的变量封装成“私有变量”。
用闭包实现累乘运算,并且具有缓存机制。
var mult = (function (){
var cache = {}; // 缓存结果
// 提炼小函数
var calculate = function () {
var sum = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
sum *= arguments[i];
}
return sum;
}
return function () {
var args = Array.prototype.join.call(arguments, ',');
if (cache[args]) {
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
})();
2.2 面向对象设计
过程与数据的结合是形容面向对象中的“对象”时经常使用的表达。对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据。
var extent = function () {
var value = 0;
return {
call: function () {
value++;
console.log(value);
}
}
}
2.3 闭包和内存管理
-
选择闭包的一部分原因是我们主动把一些变量封闭在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的。如果将来需要回收这些变量,可以手动把这些变量设为 null。
-
闭包和内存泄露有关系的地方是:使用闭包不当,容易形成循环引用。
三、高阶函数
高阶函数是指至少满足下列条件之一的函数:
- 函数可以作为参数传递
- 函数可以作为返回值输出
3.1 函数作为参数传递
把函数当作参数传递,代表我们可以抽离出一部分容易变化的业务场景,把这部分业务逻辑放在函数参数中,分离业务代码中变化与不变的部分。
场景举例:回调函数、Array.prototype.sort
3.2 函数作为返回值
判断数据类型
var isType = function (type) {
return function (obj) {
return Object.prototype.toString.call(obj) === `[object ${type}]`;
}
}
var isString = isType('String');
var isArray = isType('Array');
3.3 实现 AOP
AOP (面向切面编程) :把一些跟核心业务逻辑模块无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中。
通常在 JavaScript 中实现 AOP,都是指把一个函数“动态织入”到另外一个函数之中。
Function.prototype.before = function(beforefn) {
var __self = this; // __self 表示原函数
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(‘something’);
}
func.before(function(){
console.log('before');
}).after(function(){
console.log('after');
})
3.4 柯里化(currying)
一个 currying 的函数首先会接收一些参数,但不会立即求值,而是返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来,等到函数真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。
以下是一个示例:用于实现灵活地 mult()
mult(1,2)(3)();
mult(1)(2,3)();
mult(1)(2)(3)();
var currying = function (fn) {
var args = [];
function next() {
if (arguments.length === 0) {
const res = fn.apply(this, args);
args = []; // 计算完毕后,清空缓存的所有参数。
return res;
} else {
[].push.apply(args, arguments);
return next;
}
}
return next;
}
var calculate = function () {
var sum = 1;
for (var i = 0, l = arguments.length; i < l; i++) {
sum *= arguments[i];
}
return sum;
}
var mult = currying(calculate);
mult(1,2)(3)();
mult(1)(2,3)();
mult(1)(2)(3)();
有的文章是用以下方式,可以使 mult(1)(2)(3)()
-> mult(1)(2)(3)
。
但有个弊端是,toString() 的调用时机最晚,如果连续多次调用 mult() 方法,将导致参数是共享的。
next.toString = function () {
const res = fn.apply(this, args);
return res;
}

3.5 uncurrying
Function.prototype.uncurrying = function () {
var self = this;
return function() {
var obj = Array.prototype.shift.call(arguments);
return self.apply(obj, arguments);
}
}
另外的版本:
Function.prototype.uncurrying = function () {
var self = this;
return function() {
return Function.prototype.call.apply(self, arguments);
}
}
3.6 节流函数
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);
}
}
3.7 分时函数
var timeChunk = function (arr, fn, count) {
var obj,
t;
var len = ary.length;
var 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 (arr.length === 0) {
return clearInterval(t);
}
start();
})
}
}
3.8 惰性加载函数
重写函数。