标签:
当某个函数被调用时,会创建一个执行环境及相应的作用域链。
执行环境(execution context)定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scope chain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境。但是有其他办法来延长作用域链。有些语句可以在作用域链的前端临时增加一个变量对象,该变量对象会在代码执行后被移除。当执行流进入下列两种语句时,作用域链就会得到加长:
这两个语句都会在作用域链的前端添加一个变量对象。对 with 语句来说,会将指定的对象添加到作用域链中。对 catch 语句来说,会创建一个新的变量对象,其中包含的是被抛出的错误对象的声明。
在 IE8 及之前版本的 JavaScript 实现中,存在一个与标准不一致的地方,即在catch 语句中捕获的错误对象会被添加到执行环境的变量对象,而不是 catch 语句的变量对象中。换句话说,即使是在 catch 块的外部也可以访问到错误对象。 IE9 修复了这个问题。
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况有所不同,因为在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中,参考下面的代码:
function createComparisonFunction(propertyName) { return function(object1, object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2){ return -1; } else if (value1 > value2){ return 1; } else { return 0; } }; }
当下列代码执行时,包含函数与内部匿名函数的作用域链如图所示:
var compare = createComparisonFunction("name"); var result = compare({ name: "Nicholas" }, { name: "Greg" });
当createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。直到匿名函数被销毁后, createComparisonFunction()的活动对象才会被销毁:
compare = null; //解除对匿名函数的引用(以便释放内存)
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,所以在绝对必要时再考虑使用闭包。
作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。因为闭包所保存的是整个变量对象,而不是某个特殊的变量:
function createFunctions(){ var result = new Array(); for (var i=0; i < 10; i++){ result[i] = function(){ return i; }; } return result; }
这个函数会返回一个函数数组,且每个函数都返回 10。
在闭包中使用 this 对象也可能会导致一些问题。我们知道, this 对象是在运行时基于函数的执行环境绑定的,而匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window(在通过 call()或 apply()改变函数执行环境的情况下, this 就会指向其他对象),看下面的例子:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); //"The Window"(在非严格模式下)
把外部作用域中的 this 对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了,如下所示:
var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ var that = this; return function(){ return that.name; }; } }; alert(object.getNameFunc()()); //"My Object"
this 和 arguments 也存在同样的问题。如果想访问作用域中的 arguments 对象,必须将对该对象的引用保存到另一个闭包能够访问的变量中。
闭包和作用域链(《JavaScript 高级程序设计》读书笔记)
标签:
原文地址:http://www.cnblogs.com/crucify-lee/p/4521273.html