根据《高程》中所讲:闭包是指有权访问另一个函数作用域中的变量的函数。 《Javascript 权威指南》中指出,从技术角度讲,所有的 javascript 函数都是闭包。 闭包,之前感觉很神秘,今天我们来揭开它的面纱,看看究竟干了什么!
我们举个例子:
1 2 3 4 5 6 7 8 function scope ( ) { let a = 1 ; return function ( ) { return a; }; } let foo = scope();foo();
根据前面所写的《执行上下文》中我们可以找到当解析代码时,会执行上下文栈,那我们按照执行上下文来看一下函数内部都做了哪些事情。
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 var scope = [];scope.push(globalContext = { this : <Global Object > , LE: { ER: { scope: <func>, foo: <uninitialized>, }, outer: null }, VR: { ER: {}, outer: null } }); scope.push(<scope>, scopefunctionContext = { this: <Global Object>, LE: { ER: { arguments: { length: 0 } }, outer: <globalContext> }, VR: { ER: {}, outer: null } }); scope.pop(); scope.push(<foo>, foofunctionContext = { this: <Global Object>, LE: { ER: { arguments: { length: 0 } }, outer: <scopefunctionContext> }, VR: { ER: {}, outer: null } }); scope.pop();
其实会很好奇,scope 函数执行完后明明已经在执行栈中移除了,为什么 foo 函数依旧能访问到其内部的变量。是因为 foo 函数的对外部引用是 scope 的词法环境,这个环境还没有消失。正因为 JS 有这个特点,所以才会生成闭包这个概念。
下面举几个常用的面试题,来自《高程》:
1 2 3 4 5 6 7 8 9 10 11 12 function createFunctions ( ) { var result = new Array (); for (var i = 0 ; i < 10 ; i++) { result[i] = function ( ) { console .log(i); }; } return result; } createFunctions().forEach((item ) => { item(); });
运行上述之后,发现最后输出 10 个 10,为什么不是 0-9 呢?我们来分析一下,已 result[0]为例,在 result[0]运行之前,全局上下文是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var scope = [];scope.push(globalContext = { this : <Global Object > , LE: { ER: { createFunctions: <func>, }, outer: null }, VR: { ER: { result: [....], i: 10 }, outer: null } })
当 result[0]运行时,它的函数上下文发生变化:
1 2 3 4 5 6 7 8 9 10 11 functionContext = { this : <Global Object > , LE: { ER: {}, outer: <createFunctionsContext> }, VR: { ER: {}, outer: <createFunctionsContext> } }
由于 result[0]中没有定义 i,所以就会向外部的词法环境中查找,最后找到 i,输出 10。
如果想输出预期结果 0-9,高程中也给出了解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function createFunctions ( ) { var result = new Array (); for (var i = 0 ; i < 10 ; i++) { result[i] = (function (num ) { return function ( ) { console .log(num); }; })(i); } return result; } createFunctions().forEach((item ) => { item(); });
当再次执行 result[0]时,这个匿名函数的上下文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 functionContext = { this : <Global Object > , LE: { ER: { 匿名function: <func> }, outer: <createFunctionsContext> }, VR: { ER: { arguments: { 0: 0, length: 1 }, num: 0 }, outer: <createFunctionsContext> } }
当匿名函数执行时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 匿名functionContext = { this : <Global Object > , LE: { ER: {}, outer: <functionContext> }, VR: { ER: { arguments: { length: 0 }, num: 0 }, outer: <functionContext> } }
同样其内部没有 num 变量,那它就会去外部的此法环境中查找,找到了 functionContext,functionContext 内部的 num 为 0,则输出 0。这样就完美解决了这个问题,也体现了闭包的作用,但现在有 let 了这种就用的很少了。闭包虽然能解决一些问题,但是尽量还是要少用闭包,因为其外部的词法环境已经销毁了,但其内部还在引用,这样的话闭包过多会造成内存泄漏,最简单直接的办法就是,执行完后设置为 null,这样就销毁了。