闭包在js语言中总是有些难以掌握其中的门道,然而js中的闭包无处不在;
MDN对闭包的定义为:闭包是指那些能够访问自由变量的函数。
自由变量: 自由变量是指在函数中使用的,但挤不上函数参数也不是函数的局部变量的变量。 闭包 = 函数 + 函数能够访问的自由变量(理论上的闭包)
ECMAScript中,闭包指的是:
从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
从实践角度:以下函数才算是闭包: 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回) 在代码中引用了自由变量 小师傅曾告诉我说闭包只要死记一条:当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是当前词法作用域之外执行
function foo() { let a = 2; function bar() { console.log(a); } return bar;}let baz = foo();baz();//2 ----这就是闭包的效果复制代码
函数bar()的词法作用域能够访问foo()的内部词法作用域,然后我们将bar()函数本身当做一个值类型进行传递。bar()可以正常执行但是是在自己定义的词法作用域之外执行。
上述代码的执行过程:
1、创建全局执行上下文,全局执行上下文压入执行上下文栈; 2、全局执行上下文进行初始化; 3、执行foo函数,创建foo函数执行上下文,foo之执行上下文被压入执行上下文栈; 4、foo执行上下文初始化,创建变量对象,作用域链,this等; 5、foo函数执行完毕,foo执行上下文从执行上下文栈中弹出 6、创建函数bar,创建bar函数执行上下文,bar执行上下文被压入执行上下文栈 7、bar执行上下文初始化,创建变量对象,作用域链、this等; 8、bar函数执行完毕,bar函数从执行上下文栈中弹出;
foo()在执行后,通常会期待foo()的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收机制用来释放不在使用的内存空间,由于foo()的内容看上去不会在被利用所以会考虑对其进行回收。而闭包正是“阻止”这件事的发生,事实上上面的例子中foo()内部作用域依然存在,那这是为什么呢?
因为bar()所声明的位置所赐,他拥有涵盖foo()内部作用域的闭包,即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回),以供bar()在之后的任何时间进行引用。bar()依然持有对该作用域的引用,而这个引用就叫做闭包闭包使得函数可以继续访问定义时的词法作用域。
当bar函数执行的时候,foo()函数上下文已经被销毁(即从执行上下文栈中被弹出),了解具体的执行程序后,我们知道bar执行上下文维护了一个作用域链:
barContext = { Scope:[AO,fooContext.AO,globalContext.VO]}复制代码
因为这个作用域链,bar函数依然可以读取到fooContext.AO的值,说明当bar函数引用了fooContext.AO中的值的时候,即使fooContext被销毁了,js依然关于让fooContext.AO或在内存中,bar函数依然可以通过bar函数的作用域链找到他,正因为JavaScript做到这一点,从而实现了闭包这个概念。
无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。
function foo() { let a = 2; function baz() { console.log(a) } bar(baz);}function bar(fn){ fn();//2 ----这里也是闭包的效果}复制代码
无论通过何种手段将内部函数传递到所在词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包。
面试题
var data = [];for(var i = 0; i<3; i++) { data[i] = function() { console.log(i) };}data[0]() //3复制代码
data[0] = function(){console.log(i)},这里的i已经在循环结束的时候变成了3。
这个例子在ES6深入学习(一)块级作用域详解[]中也讲过,有兴趣可以看一看~