这篇算接上一篇:【JavaScript 预编译:函数声明提升,变量声明提升】
说闭包之前我们先介绍几个概念:
1. 执行期上下文
当函数执行时(实际上是函数执行前一刻)会创建一个称为执行期上下文的对象(就是上一篇介绍的 Activation Object),一个执行期上下文定义了一个函数执行期间的环境。当函数执行完毕,它所产生的执行期上下文被销毁。
函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文。
2. 作用域( [[scope]] )
每个 JavaScript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以。不可以访问的这些属性仅供 JavaScript 引擎存取,[[scope]] 就是其中一个。
[[scope]] 指的就是我们所说的作用域,其中存储了执行期上下文的集合。
3. 作用域链
[[scope]] 中所存储的执行期上下文对象的集合呈链式连接,我们把这种链式连接叫做作用域链。查找变量就是从作用域链的顶端依次向下查找。
下面来看一个例子:
function a() {
function b() {
var b = 234;
console.log(b);
}
var a = 123;
b();
console.log(b);
}
console.log(a);
var glob = 100;
a();
-
函数 a 被定义时发生如下过程:(此时 a 在全局的执行环境中,a 的作用域链上只有一个 GO 对象)
其中 Global Object 中还应该有 window,document 等对象,这里暂且不管,下同。
-
函数 a 执行时发生如下过程:(a 执行的前一刻会创建一个它自己的执行上下文 AO 对象,并且将这个对象放在作用域链的最顶端)
其中 Activation Object 中还应该有 arguments 等,这里暂且不管,下同。
-
函数 b 被定义时发生的过程跟函数 a 执行时发生的过程是一样的,此时 b 在 a 的执行环境中。(此时 b 的作用域链上的 AO 对象跟 a 的作用域链上 AO 对象是同一个,都是 a 的)
-
函数 b 执行时发生的过程如下:(b 执行的前一刻会创建一个它自己的执行上下文 AO 对象,并且将这个对象放在作用域链的最顶端,其他对象依次下移)
当函数 a 函数 b 执行完成后,他们所产生的执行期上下文全部都会被销毁。
再看一个例子:
function a() {
function b() {
var bbb = 234;
console.log(aaa);
}
var aaa = 123;
return b;
}
var glob = 100;
var demo = a();
demo();
-
函数 a 被定义时发生如下过程:
-
函数 a 执行时发生如下过程:
-
函数 b 被定义时发生的过程跟函数 a 执行时发生的过程是一样的。
-
函数 demo/b 执行时发生的过程如下:
值得注意的是,在函数 a 执行完成后将函数 b 作为返回值赋给了全局变量 demo。函数 a 执行完成后会销毁自己的执行期上下文,但是被保存到外部的函数 b 的作用域链上还是能够访问到 aaa。所以当 demo 执行的时候也就是 b 执行的时候,能够输出 aaa 的值 123。 这就是我们说的闭包。
闭包
当内部函数被保存到外部时,仍然能够访问到原来包含函数内部的变量,就会形成闭包。
闭包会导致原有作用域链不释放,造成内存泄漏。(内存泄漏就是,内存被占用的越来越多,可供使用的越来越少。)
闭包为什么会造成内存泄漏?(这个纯粹是我个人的理解,不知道对不对)
js 垃圾收集机制的原理是找出那些不再继续使用的变量,释放其占用的内存。
按照上面的例子来说,函数 b 执行完成之后本应该释放其作用域链,但是由于函数 b 总是被 变量 demo 引用,所以 js 就认为函数 b 一直是有用的,一直不回收它。因此造成了内存泄漏。
闭包的作用:
- 实现公有变量;eg:函数累加器
- 可以做缓存(存储结构);eg:eater
- 可以实现封装(属性私有化);eg:Person
- 模块化开发(防止污染全局变量)
前两个作用对应的例子:
eg:函数累加器
function add() {
var count = 0;
function fnCount() {
count++;
console.log(count);
}
return fnCount;
}
var counter = add();
counter();
counter();
counter();
counter();
counter();
...
或者
var counter = null;
function add() {
var count = 0;
counter = function() {
count++;
console.log(count);
}
}
add();
counter();
counter();
counter();
counter();
counter();
...
eg:eater
function eater() {
var food = '';
var handFood = {
eating: function() {
if(food){
console.log('I am eating ' + food);
}
else{
console.log('Nothing to eat!');
}
},
pushFood: function(myfood) {
food = myfood;
}
};
return handFood;
}
var myEater = eater();
myEater.eating();
myEater.pushFood('banana');
myEater.eating();