根据《高程》中所讲:闭包是指有权访问另一个函数作用域中的变量的函数。 《Javascript权威指南》中指出,从技术角度讲,所有的javascript函数都是闭包。 闭包,之前感觉很神秘,今天我们来揭开它的面纱,看看究竟干了什么! 我们举个例子:
function scope() {
let a = 1;
return function () {
return a;
}
}
let foo = scope();
foo();
根据前面所写的《执行上下文》中我们可以找到当解析代码时,会执行上下文栈,那我们按照执行上下文来看一下函数内部都做了哪些事情。
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有这个特点,所以才会生成闭包这个概念。
下面举几个常用的面试题,来自《高程》:
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]运行之前,全局上下文是这样的:
var scope = [];
scope.push(globalContext = {
this: <Global Object>,
LE: {
ER: {
createFunctions: <func>,
},
outer: null
},
VR: {
ER: {
result: [....],
i: 10
},
outer: null
}
})
当result[0]运行时,它的函数上下文发生变化:
functionContext = {
this: <Global Object>,
LE: {
ER: {},
outer: <createFunctionsContext>
},
VR: {
ER: {},
outer: <createFunctionsContext>
}
}
由于result[0]中没有定义i,所以就会向外部的词法环境中查找,最后找到i,输出10。
如果想输出预期结果0-9,高程中也给出了解决方案:
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]时,这个匿名函数的上下文:
functionContext = {
this: <Global Object>,
LE: {
ER: {
匿名function: <func>
},
outer: <createFunctionsContext>
},
VR: {
ER: {
arguments: {
0: 0,
length: 1
},
num: 0
},
outer: <createFunctionsContext>
}
}
当匿名函数执行时:
匿名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,这样就销毁了。