在 Javascript 语言中,闭包就是一个函数,只是它上下文中的变量以引用的形式与其绑定在了一起。
function getMeAClosure() {
var canYouSeeMe = "here I am";
return (function theClosure() {
return {canYouSeeIt: canYouSeeMe ? "yes" : "no"};
});
}
var closure = getMeAClosure();
closure().canYouSeeIt; //"yes"
实际上每个 Javascript 函数在生成的时候都形成了闭包。稍后我会给大家解释闭包的产生原因和过程,然后纠正一些关于闭包的错误概念,最后再给出一些闭包的实际用例。不过首先简单介绍一下闭包相关的基础概念: Javascript 的闭包是通过 词法域 (lexical scope)
和 变量环境 (VariableEnvironment)
实现的。
“词法”这个词一般都是语言相关。所以函数的词法域是静态的,是由函数代码在源代码中的位置决定的。
参考以下代码:
var x = "global";
function outer() {
var y = "outer";
function inner() {
var x = "inner";
}
}
函数 inner
在代码中被函数 outer
包裹着,而 outer
又被全局上下文包含在内。这样就形成了一个词法继承关系:
global
— outer
—— inner
每个函数的外部词法域都是由词法继承关系中它的祖先决定的。因此,inner
函数的外部词法域就是由全局对象和函数 outer
组成的。
全局对象有一个相关的执行上下文。而且每一次函数调用也会建立并进入一个新的执行上下文。这个执行上下文相对于静态的词法域是动态生成的。每一个执行上下文都确定了一个变量环境,它是在该上下文中所声明变量的容器。(ES 5 10.4 , 10.5 )
注意,在 EcmaScript 3 中,函数的变量环境( VariableEnvironment )被称为活动对象( ActivationObject )
以下伪代码可以用来描述变量环境
//variableEnvironment: {x: undefined, etc.};
var x = "global"
//variableEnvironment: {x: "global", etc.};
function outer() {
//variableEnvironment: {y: undefined};
var y = "outer";
//variableEnvironment: {y: "outer"};
function inner() {
//variableEnviroment: {x: undefined};
var x = "inner";
//variableEnvironment: {x: "inner"};
}
}
不过,这只描述了整个图景中的一部分。每个变量环境都会继承它所属词法域的变量环境。
当一个函数定义过程发生在某个执行上下文环境中时,会生成一个新的函数对象,此函数对象会包含一个名为 [[scope]] 的内部属性引用当前的变量环境。(ES 5 13.0-2 )
每个函数都有这样一个 [[scope]] 属性,而且当函数被调用时,这个 [[scope]] 属性会被赋值给变量环境的 outerLex 属性,该属性是对外层词法环境的引用( outer lexical environment reference 简写为 outerLex )。这样一来,每个变量环境都继承了它父级的变量环境。这个 [[scope]] 链会一直延伸至全局对象,与词法继承的长度一样。
现在让我们再来看一下伪代码:
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.