图解javascript中的变量对象、闭包、作用域链机理


什么是闭包

javascript中的闭包是一个强大而灵活的武器,搞清闭包作用域链的作用机理,能让我更好的将闭包运用在我们的项目中。

mozilla开发者中心的定义:

Closures are functions that refer to independent (free) variables.

In other words, the function defined in the closure ‘remembers’ the environment in which it was created.

我理解的闭包是一个function,并且具有访问不在function内部定义的变量的能力,如嵌套function中的子function可以访问到定义在父function中的变量,是一类语言(如js)的特性。

闭包的应用

看一个闭包在js for循环中经典的应用:


function foo(){
	for(var i = 0; i<10; i++){
		(function(j){
			setTimeout(function(){
				console.log( "current i:" + j + "--" + new Date().getSeconds() + "s" );
			}, j * 1000);
		})(i);
    }
}

foo();

上面的代码改自Pro JavaScript Techniques 中用js控制css达到动画效果的部分。动画的高度/透明度是根据索引i的值动态设置的,所以我们需要将这个索引i保存下来。

闭包的错误使用

这里就有一个问题,为什么我们写成下面的代码就不能得到正确的索引呢?

function foo(){
	for(var i = 0; i<10; i++){
		setTimeout(function(){
				console.log("current i:"+i+"--"+new Date().getSeconds()+"s");     //这里也用到了闭包
		},i*1000);
    }
}

foo();

上面的代码得到的i始终是10,而不是想要的1,2,3

现在我们来逐步详细分析原因:

    1. 进入fooexecution context阶段:

这时创建fooVariable Object (VO)/Activation Object (AO)

    VO(foo) = {
          i: undefined,
    };
    1. foo代码执行阶段:

fooExecutionContext push进Execution Context Stack 中,i随着循环被修改为相应的数值。

    executionContextStack.push(fooExecutionContext);

    executionContextStack = [ 
        <foo> functionContext,
        globalContext
    ]

由于在for循环执行的时候,setTimeout内部的匿名函数的execution context对于foo来说是不可见的,因为这时的匿名函数并没有执行, 不能访问、修改该匿名函数内部的变量,所以匿名函数中的i不会被修改为for循环的当前索引。 但是该匿名函数的Variable Object (VO)/Activation Object (AO)已经创建,并且保存了i引用

for循环结束时i值为10, 正是由于匿名函数的VO/AO保存了i引用foo运行结束时,Garbage Collector不会销毁fooVO/AO(上面保存着i=10),所以当setTimeout内的匿名函数运行时,i的值始终为10

搞清楚了上面的问题后,现在我们用图来解释开始的例子。

图1

上面的图是foo在执行最后一次循环时的运行机理。

图2

图2是setTimeout内部匿名函数执行时的机理,其中红颜色框起来部分随每个setTimeout内部匿名函数的不同而不同。

通过上面两副图我们可以清楚的看到,增加的匿名自执行函数的作用就是将for循环的索引作为自己的局部变量保存起来,这样setTimeout里面的匿名函数就可以通过scope chain访问到正确的索引值了。

参考

http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

http://dmitrysoshnikov.com/ecmascript/chapter-1-execution-contexts/

http://dmitrysoshnikov.com/ecmascript/chapter-2-variable-object/

http://dmitrysoshnikov.com/ecmascript/chapter-4-scope-chain/

迁移自旧博客懒蜗牛


文章作者: Caden
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Caden !
 上一篇
Android PluginManager 源码解析1--PluginManager Android PluginManager 源码解析1--PluginManager
Android PluginManager简介Android pluginManager是HouKangxi大神开发的一个android插件化开发框架,可以动态的加载apk,实现组件的热插拔。 项目地址:https://github.com
2015-08-09
下一篇 
android不同系统版本sd的挂载方式 android不同系统版本sd的挂载方式
2.x系统sd卡实际挂载位置 /mnt/sdcard 并建立了一个/sdcard的软链接指向/mnt/sdcard /sdcard/ --> /mnt/sdcard Android 2.2之后的版本允许将应用程
2014-11-01
  目录