1 什么是闭包
关于什么是闭包有下面两种定义方式:
- 有能力引用外部变量的函数叫做闭包,因为 js 的函数都具有此能力,故 js 的所有函数都可称作做闭包
- 引用了外部变量的函数叫做闭包
个人更倾向于第二种定义方式,有下面两个原因:
- 第二种定义会使得理解 js 中的闭包相关问题时更顺畅
- 只有当 js 函数引用了外部变量时,其作用域链 [[scope]] 才会包含一个叫做 Closure 的对象(Closure 对象中存储了被引用的外部变量,稍后会有图示)。
在下面示例中,B1 就是一个闭包(因为 B1 引用了外部函数 A 的变量),可以看到 B1 的作用域链包含了一个 Closure 成员。
1 2 3 4 5 6 7 8 9 10
| function A(){ var aVar = 'aVar'; return function B1(){ console.log(aVar); } }
var B1Alias = A(); console.dir(B1Alias);
|
2 闭包的用途
2.1 封装私有变量、方法
js 中可以使用函数来封装模块,其实就是利用了闭包的特性。下面代码就使用函数封装了内部的私有变量 privateCounter 和私有方法 changeBy。外部只能通过 makeCounter 返回的公共方法操作私有变量。
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 27
| var makeCounter = function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } } };
var Counter1 = makeCounter(); var Counter2 = makeCounter(); console.log(Counter1.value()); Counter1.increment(); Counter1.increment(); console.log(Counter1.value()); Counter1.decrement(); console.log(Counter1.value()); console.log(Counter2.value());
|
3 闭包中常见的问题
在使用闭包时,很容易出现一些意料之外的执行结果,并且影响性能,容易造成内存泄露,所以应该尽量少使用闭包。
3.1 在循环中创建闭包
在循环中创建闭包时,如果将循环代码直接用于闭包,则会使所有闭包指向同一个变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function constFuns() { var funs = [];
for (var i = 0; i < 10; i++) { funs[i] = function() { return i; }; } return funs; } var funs = constFuns(); console.dir(funs[5]()); console.dir(funs);
|
解决上面问题的方法:
1、使用 es6 的 let 语法,从而防止产生闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function constFuns() { var funs = [];
for (let i = 0; i < 10; i++) { funs[i] = function() { return i; }; } return funs; } var funs = constFuns(); console.dir(funs[5]()); console.dir(funs);
|
可以看到,匿名函数的作用域链中,并没有包含 Closure 成员,而是包含了并不共享的 Block 成员。从而避免了 Closure 共享所带来的问题。
2、使用更多的闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function constFuns() { var funs = []; function inner(i) { return function() { return i; }; }
for (var i = 0; i < 10; i++) { funs[i] = inner(i); }
return funs; }
var funs = constFuns(); console.log(funs[5]()); console.dir(funs);
|
3.2 影响性能
闭包会对降低程序的处理速度、增加对内存的使用。
3.3 内存泄露
代码中有循环引用时,容易操作内存泄露。
4 注意
4.1 嵌套函数没有使用父函数的变量时,不会产生闭包属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function A(aArg){ var aVar = 'aVar'; function B1(b1Arg){ var b1Var = 'b1Var'; }
function B2(b2Arg){ var b2Var = 'b2Var'; }
console.dir(B1); console.dir(B2); }
A();
|
输出:
4.2 嵌套函数在引用父函数的变量后,会产生闭包属性,并且同一外部函数内的嵌套函数共享同一个作用域链(即都会产生闭包属性)。
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 27 28 29 30 31
| function A(aArg){ var aVar = 'aVar';
function B1(b1Arg){ var b1Var = 'b1Var';
function C1(c1Arg){ var c1Var = 'c1Var'; }
console.dir(C1); }
function B2(b2Arg){ var b2Var = 'b2Var'; console.log(aVar); }
console.dir(B1); console.dir(B2); B1(); }
A();
|
chrome 输出: