定义 闭包在 MDN 中的定义:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
这里只有一类函数除外,那就是通过 Function 构造器创建的函数,因为其[[Scope]]只包含全局对象。
为了更好的澄清该问题,我们对 ECMAScript 中的闭包给出 2 个正确的版本定义:
ECMAScript 中,闭包指的是:
从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量
下面我们主要讨论实践上的闭包。
分析 这里我们需要注意的是:在 ECMAScript 中,同一个父上下文中创建的闭包是共用一个[[Scope]]属性的。也就是说,某个闭包对其中[[Scope]]的变量做修改会影响到其他闭包对其变量的读取:
这就是说:所有的内部函数都共享同一个父作用域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var firstClosure;var secondClosure;function foo ( ) { var x = 1 ; firstClosure = function ( ) { return ++x; }; secondClosure = function ( ) { return --x; }; x = 2 ; console .log(firstClosure()); } foo(); console .log(firstClosure()); console .log(secondClosure());
因为我们经常会遇到这样一个面试题:
1 2 3 4 5 6 7 8 9 10 11 var data = [];for (var k = 0 ; k < 3 ; k++) { data[k] = function ( ) { console .log(k); }; } data[0 ](); data[1 ](); data[2 ]();
上述当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
1 2 3 4 5 6 globalContext = { VO : { data : [...], k : 3 } }
当执行 data[0] 函数的时候,data[0] 函数的作用域链为:
1 2 3 data[0 ]Context = { Scope : [AO, globalContext.VO] }
data[0]Context 的 AO 并没有 k 值,所以会从 globalContext.VO 中查找,k 为 3,所以打印的结果就是 3。data[1] 和 data[2] 是一样的道理。 如下所示,创建一个闭包就可以解决这个问题了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var data = [];for (var k = 0 ; k < 3 ; k++) { data[k] = (function _helper (x ) { return function ( ) { console .log(x); }; })(k); } data[0 ](); data[1 ](); data[2 ]();
当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
1 2 3 4 5 6 globalContext = { VO : { data : [...], k : 3 } }
跟之前一样,但是 data[0]的作用域链发生了变化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 data[0 ]Context = { Scope : [AO,_helperContext.AO, globalContext.VO] } _helperContext = { VO : { arguments : { 0 : 0 , length : 1 }, k : 0 }, Scope : [AO, globalContext.VO] }
data[0]Context 的 AO 并没有 k 值,所以会沿着作用域链从_helperContenx.AO 中查找,这时候找到 k 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 K 的值(值为 3),所以打印的结果就是 0。同理 data[1]、data[2]的值就是 1 和 2。
闭包用法实战 实际使用的时候,闭包可以创建出非常优雅的设计,允许对funarg上定义的多种计算方式进行定制。如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 [1 , 2 , 3 ].sort(function (a, b ) { ... }); [1 , 2 , 3 ].map(function (element ) { return element * 2 ; }); someCollection.find(function (element ) { return element.someProperty == 'searchCondition' ; }); [1 , 2 , 3 ].forEach(function (element ) { if (element % 2 != 0 ) { console .log(element); } }); (function ( ) { alert([].join.call(arguments , ';' )); }).apply(this , [1 , 2 , 3 ]); var a = 10 ;setTimeout (function ( ) { alert(a); }, 1000 ); var x = 10 ;xmlHttpRequestObject.onreadystatechange = function ( ) { alert(x); }; var foo = {};(function (object ) { var x = 10 ; object.getX = function _getX ( ) { return x; }; })(foo); alert(foo.getX());
参考文献 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures https://juejin.cn/post/6844903475998900237 http://dmitrysoshnikov.com/ecmascript/chapter-6-closures/