首先上一段理论,看不懂可以先不看,直接看例子。
以下几个概念:
执行环境
每调用一个函数时(执行函数时),系统会为该函数创建一个封闭的局部的运行环境,即该函数的执行环境。函数总是在自己的执行环境中执行,如读写局部变量、函数参数、运行内部逻辑。创建执行环境的过程包含了创建函数的作用域,函数也是在自己的作用域下执行的。从另一个角度说,每个函数执行环境都有一个作用域链,子函数的作用域链包括它的父函数的作用域链。
简单来说,作用域链就是函数在定义的时候创建的,用于寻找使用到的变量的值的一个索引,而他内部的规则是,把函数自身的本地变量放在最前面,把自身的父级函数中的变量放在其次,把再高一级函数中的变量放在更后面,以此类推直至全局对象为止.当函数中需要查询一个变量的值的时候,js解释器会去作用域链去查找,从最前面的本地变量中先找,如果没有找到对应的变量,则到下一级的链上找,一旦找到了变量,则不再继续.如果找到最后也没找到需要的变量,则解释器返回undefined.
了解了作用域链,我们再来看看js的内存回收机制,一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了.对应的内存空间也就被回收了.下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.但是如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的.并且这个内部函数又使用了外部函数的某些变量的话.这种内存回收机制就会出现问题.如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来.也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收.
闭包在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。闭包的定义:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
闭包就是嵌套在函数里面的内部函数,并且该内部函数可以访问外部函数中声明的所有局部变量、参数和其他内部函数。当该内部函数在外部函数外被调用,就生成了闭包。(实际上任何函数都是全局作用域的内部函数,都能访问全局变量,所以都是window的闭包)
来看实际的例子。
var result = [];
function foo() {
for (var i = 0;i < 3;i++) {
result[i] = function() {
console.log(i);
}
}
};
foo();
result[0](); // 3
result[1](); // 3
result[2](); // 3
按照上面的理解,存在数组中的匿名函数和i就构成了closure。但这个闭包不是我们所想要的结果。因为我们想输出的是0,1,2。然而实际输出的却是3,3,3。
很明显,i在循环中的值并没有被保存下来,保存下来的是i最后循环完的值。那按照最后一段话的理解,我们就要用另一种闭包的方式来存储循环过程中的i的值。
具体方法可以见下面。
<pre name="code" class="javascript">var result = [];
function foo() {
for (var i = 0;i < 3;i++) {
result[i] = (function(j) {
return function() {
console.log(j);
};
})(i);
}
};
foo();
result[0](); // 0
result[1](); // 1
result[2](); // 2
可以看到,在原来的函数外又嵌套了一层函数,通过j这个本地变量保存了循环过程中i的值。
此时闭包真正实现了我们所想要的结果。
其实还有一种方式可以达到上面的效果。
var result = [];
function foo() {
for (var i = 0;i < 3;i++) {
let s = i;
result[i] = function() {
console.log(s);
}
}
};
foo();
result[0](); // 0
result[1](); // 1
result[2](); // 2
上面let声明的s变量 达到了同样的效果,具体这里就不阐述了。