javascript执行上下文、作用域与闭包(第六篇)
终于讲到闭包了,当你在百度上搜索闭包时,你会被搜索出来的结果吓一跳,我的天,为什么说得都不一样?直到把所有的解释都看过了,我就只想说一句,到底谁说的是对的…
在这么多的不同解释里,我认真思考了很久,到底该相信谁?最后我选择相信大道至简,因为我始终觉得理论来源于实践,而实践一定不是在象牙塔里,而是可以摸得到的简单的东西。
下面就来讲最原始的闭包的示例:
function fn(){var max=10;return function bar(x){if(x>max){alert(x);}}}var f1=fn();f1(15);//15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
如上代码,bar函数作为返回值,赋值给f1变量。执行f1(15)时,就相当于执行bar(15),沿着作用域链取到fn作用域下的max变量的值。
这已经产生了一个最基本的闭包,用自然语言描述的话就是:
(1)定义普通函数 A
(2)在 A 中定义普通函数 B
(3)在 A 中返回 B
(4)执行 A, 并把 A 的返回结果赋值给变量 C
(5)执行 C
而它的形式就是用“return”作为桥梁,链接A外的变量C和A内的变量B。
用一个归纳的话说就是:
当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包
如果你认为这就是闭包的全部,那你就有些狭隘了。
下面一个例子,就展示了这个狭隘之处:
function A(){ var count=0; function B(){ count++; alert(count); } return B;} var c=A(); c();//1 c();//2 c();//3
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
那么你就会惊奇于闭包还有如此神秘的一面,其实对于这一面,网上有很多解释,解释得五花八门,但大都没解释到本质上。
其实这一系列的文章的标题都是“javascript执行上下文、作用域与闭包”,说明闭包与执行上下文,作用域是有紧密联系的,下面就来演示一下闭包那神秘的一面的本质。
咱们可以拿本文的第一段代码(稍作修改)来分析一下。
function fn(){var max=10;return function bar(x){if(x>max){alert(x);}}}var f1=fn(),max=100;f1(15);//15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
我们来一步一步揭开那神秘的面纱:
在这之前,告诉大家一个很重要的东西—-我们在前面讲执行上下文栈时当一个函数被调用完成之后,其执行上下文将被被弹出,即销毁,其中的变量也会被同时销毁。
但我们要铭记:在闭包里,函数调用完成之后,其执行上下文环境不会立即被销毁。(这句话是不正确的,但这里读者可以先用这个错误的解释去看下面的步骤,先对闭包形成的原因有一个大致的了解,最终正确的理解在 厚积薄发—从此再也不用担心闭包问题)
第一步,代码执行前生成全局上下文环境,并在执行时对其中的变量进行赋值。此时全局上下文环境是活动状态:
第二步,执行第17行代码时,调用fn(),产生fn()执行上下文环境,压栈,并设置为活动状态。
第三步,执行完第17行,fn()调用完成。按理说应该销毁掉fn()的执行上下文环境,但是因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的执行上下文,当代码执行到return时,按理说应该把fn()的执行上下文销毁,但是由于return的bar()存在对fn()上下文的引用,(因为存在对变量max的需要)所以不能将其销毁,这里就要提到js的垃圾回收机制,当一个函数不存在外部对它的引用的时候,就自动将其销毁。这里存在对把不能把fn()上下文销毁。
第四步,执行到第18行时,全局上下文环境将变为活动状态,但是fn()上下文环境依然会在执行上下文栈中。另外,执行完第18行,全局上下文环境中的max被赋值为100。如下图:
第五步,执行到第20行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态
执行bar(15)时,max是自由变量,需要向创建bar函数的作用域中查找,找到了max的值为10,
这里的重点就在于,创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了。
可以看到,使用闭包会使变量保存在内存中,但是缺点就是会增加内存开销。
在网上有些资料解释闭包的主要用途有两个,一个是可以使外部变量访问到一个函数的内部变量,一个是使变量保存在内存中。其实这句话不太贴切,正确的说法应该是:使外部变量访问到一个函数的内部变量是闭包的形式,使变量保存在内存中是闭包的工作原理。
到这里,如果觉得有种恍然大悟的感觉,不妨试一试下面的例子
function A(){ var count=0; function B(){ count++; alert(count); } return B;} var c=A(); c();//1 c();//2 c();//3
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
大家可以试一试解释为什么三次调用c(),结果分别是1,2,3呢?
如果还有些不懂,我会在下一篇里详细讲一下我的理解。
本文参考了王福朋老师的深入理解javascript原型和闭包(15)——闭包