做防腐木花架的网站网站推广效果不好原因是
你将知道:
- 什么是闭包
- 什么是词汇环境
- Chrome 中的
[[Scopes]]属性 - 保留函数的本地环境
- 闭包的实际应用
什么是闭包? closures
函数及其词法环境统称为闭包。
什么是词汇环境?
考虑下面的代码:
var a = 'static';function f1() {console.log(a);
}function f2() {var a = 'dynamic';f1();
}f2();
是时候揭晓答案了:它是 ‘静态的’ 。
在这个作用域系统中,为了解析函数内部的名称,首先在函数的局部环境中搜索,然后在其词法环境中搜索。
函数 f 的词法环境只是指源代码中包围该函数定义的环境。
换句话说,给定函数的词法环境基于程序的源代码——函数的定义位置。这就是为什么我们称之为 “词法” 环境,即基于源代码的环境。
那么,根据源代码,它究竟是如何运作的呢?这到底是什么意思呢?
嗯,JavaScript 编译器在编译程序时会读取程序的源代码,并根据其定义确定给定函数可访问的环境。
例如,如果函数 f 在全局范围内定义,则其词法环境(编译函数时确定)就是全局环境。
包围一个函数的所有环境集合统称为该函数的词法环境。
Note: 一个函数的词法环境可以包含多个环境。
词法环境一旦确定,便会在整个程序运行过程中保持不变。这就是为什么 JavaScript 被称为静态作用域语言的原因——程序中可访问给定名称(变量和函数)的位置是静态的(即不会改变),并且由源代码控制。
让我们重新考虑一下上面显示的代码片段:
var a = 'static';function f1() {console.log(a);
}function f2() {var a = 'dynamic';f1();
}f2();
这里,函数 f1 定义在全局作用域中,同样,它的词法环境也是整个全局环境。函数 f2() 也是如此。
现在,当在第 12 行调用 f2() 时,首先创建一个局部变量 a 并将其初始化为 'dynamic' ,然后调用 f1() 。在 f1() 内部,会遇到语句 console.log(a) 。此时,必须解析名称 a 。
- 首先在
f1的局部作用域中搜索名称a。显然,由于该局部环境为空,因此未找到该名称。因此,搜索将移至f1的词法环境。 - 在词法环境中的搜索也是有序的。也就是说,首先搜索函数的第一个封闭环境,然后是更外层的封闭作用域,依此类推,直到最终到达全局环境,此时不再有进一步的封闭环境。
f1的封闭词法环境就是全局作用域,因此这里搜索的是名称a。由于找到了匹配项,并且绑定到了值20,console.log(a)中的名称a被解析为值20。
词法意味着与源代码相关,或者仅仅基于源代码。函数的词法环境仅仅指包含函数定义的环境。之所以称为词法,是因为它基于源代码,并且在整个程序执行过程中保持不变。当函数 f 中引用名称 x 时,首先在 f 的局部环境中搜索,然后在其词法环境中搜索。
Chrome 中的 [[Scopes]] 属性
根据 ECMAScript 规范,每个函数都包含一个内部属性 [[Environment]] ,它仅包含该函数的词法环境。最棒的是,这个属性是 Chrome 控制台公开的属性之一。然而,在撰写本文时,它的命名略有不同——它被称为 [[Scopes]] 。这意味着我们实际上可以通过检查其 [[Scopes]] 属性来检查 Chrome 中任何给定函数 f 的词法环境。考虑上面定义的相同代码。注意,我们暂时不调用 f2() ,同样,最后一条语句也被注释掉了。我们稍后完成一些基本检查后再调用它,让我们在代码中添加 console.dir() 语句来检查 f1 ::
var a = 'static';function f1() {console.log(a);
}function f2() {var a = 'dynamic';f1();
}// f2();
console.dir(f1);

首先,展开函数 f1 以查看其属性和内部属性。在这里,展开 [[Scopes]] 属性以查看 f1 的词法环境。
在这种情况下,它是一个仅包含一个指向全局环境的对象的数组。最后,我们展开 [[Scopes]] 的这个元素。接下来是一长串属性,其中我们只能显示前五个。
看到这里的 a 了吗? 这是全局变量 a 。在上面的代码中,当 f2 内部调用 f1 时,它被用来解析 f1 中的名称 a 。
我们来回顾一下在上面的函数 f1 中遇到 a 时会发生什么。
- 在
f1的局部环境中搜索名称a。这里没有找到任何内容,同样搜索转移到f1的词法环境。 - 在
[[Scopes]]表示的词法环境中,名称a在包含f1的第一个环境中被搜索。在本例中,该环境就是全局环境。 - 同样,在全局环境中搜索
a。找到一个匹配项,该匹配项绑定到值'static',因此f1中的a被解析为'static'。
同一作用域内的函数具有相同的词法环境 。需要记住的一点是,同一作用域内的所有函数都指向完全相同的词法环境。这是一个无关紧要的细节,无论如何都应该满足。
这意味着在上面的代码中, f2 的 [[Scopes]] 将与 f1 的 [[Scopes]] 相同,因为它们都位于全局作用域内。它们将引用存储在内存中的同一个内部环境对象。
好了,接下来,我们对上面的代码做一个简单的修改,然后重新思考一下。我们将把所有代码封装在一个 IIFE 函数中:
(function() {var a = 'static';function f1() {console.log(a);}function f2() {var a = 'dynamic';f1();}// f2();console.dir(f1);
})();

f1 (和 f2 )的 [[Scopes]] 属性包含两个条目。
第一个是包含 f1 定义的第一个环境,即 IIFE 的局部环境;
第二个是包含 f1 第二个环境,即全局环境。
为什么没有 f2
你可能会想: 为什么上面 [[Scopes]] 的第一个条目中没有 f2 呢? f2 是因为 V8 引擎(Chrome 的 JavaScript 编译器)做了优化。在这种情况下,引擎认为 IIFE 中的任何函数( f1 和 f2 )都不需要 f2 ,同样,它也没有存储在这些函数的词法环境中。
和以前一样,让我们看看当在函数 f1 中遇到名称 a 时,即调用该函数时会发生什么:
- 在
f1的局部环境中搜索名称a。这里没有找到任何内容,同样搜索转移到f1的词法环境。 - 在通过
[[Scopes]]表示的词法环境中,在包含f1第一个环境(即 IIFE 的局部环境)中搜索名称a。 - 在这里,确实发现了值为
'static'的a,同样,f1中的a也被解析为'static'。
更多示例
让我们考虑一个非常简单的例子来扩展我们之前的代码片段:
var a = 'easy';(function() {var b = 'easy';function f1() {console.log(a, b);}function f2() {var a = 'difficult';var b = 'difficult';f1();}f2();
})();
好吧,让我们看看上面的代码是如何执行的。
- 创建一个全局变量
a并赋予其值'easy',然后创建并调用一个 IIFE。 - 为这个 IIFE 创建了一个局部环境。这里创建了一个局部变量
b,并将其初始化为值'easy'。接下来,创建了两个函数f1和f2。 - 将函数的词法环境保存在函数的
[[Environment]]内部属性中 - 完成所有这些后,执行转到第 16 行,在此调用
f2()。 - 为
f2创建了一个局部环境。这里定义了两个局部变量a和b,它们的值均为'difficult'。 - 接下来,调用
f1()。 - 为
f1创建了一个局部环境。执行移至第 7 行的语句console.log(a, b)现在,在处理此语句之前,必须先解析名称a和b。 - 让我们首先解决
a:
-
- 在
f1的局部环境中搜索a。未找到匹配项,因此搜索移至词汇环境. - 在词法环境中,在
f1的第一个封闭环境(即 IIFE 的局部环境)中搜索b。 - 即使在这里也找不到任何东西,同样搜索移动到第二个封闭环境(在
f1的词汇环境内),即全局环境。 - 由于在这里找到了匹配
a = 'easy',console.log( a , b)中的名称a被解析为值'easy'。
- 在
- 现在让我们解决
b:
-
- 在
f1的局部环境中搜索b。未找到匹配项,因此搜索转移到词汇环境。 - 在词法环境中,在
f1的第一个封闭环境(即 IIFE 的局部环境)中搜索b。 - 由于在这里找到匹配项
b = 'easy',console.log(a, b )中的名称b被解析为值'easy'。
- 在
- 此时名称
a和b都已解析,因此语句console.log(a, b)执行,完成f1的执行、f2的执行,最终完成整个程序的执行。

保留函数的本地环境
“JavaScript 为什么能够在函数退出后保留其本地环境” 。
看一下下面的代码:
function f1() {var a = 'difficult';return function() {console.log(a);};
}var a = 'easy';
var f2 = f1();f2();
当执行上述代码时,会发生以下情况:
- 创建一个函数
f1,然后将全局变量a赋值为'easy'。 - 接下来,调用函数
f1()。 - 为函数
f1创建一个局部环境。在这里,定义一个变量a,其值为'difficult'。然后创建一个匿名函数,其词法环境包含这个(f1的)局部环境,最后返回这个函数。 - 此时,由于函数
f1返回了一个值,表示该函数已执行完毕,JavaScript 引擎必须删除其本地环境。在删除之前,会进行快速搜索,查找是否存在对该环境的引用。由于在函数f1的本地作用域之外,确实存在一个引用——在返回函数的[[Environment]]属性中——因此该环境会保留在内存中 。但是,其他必要的步骤仍会照常执行,例如清除f1的调用堆栈框架。 - 调用
f1()完成,其返回值(一个匿名函数)被赋值给全局变量f2。然后调用f2()。 - 为
f2创建了一个局部环境。执行到第 5 行,此时console.log(a)中的名称a应该被解析。 - 解决方法如下:
-
- 在
f1的局部环境中搜索名称a。这里没有找到任何内容,同样,搜索转移到词汇环境。 - 在词法环境中,检查
f1的第一个封闭环境(即函数f2的局部环境)是否存在a。 - 这里由于找到了匹配
a = 'difficult',所以console.log(a)中的名称a被解析为值'difficult'。
- 在
这里最有趣的一点是,即使 f1 退出(通过 return 关键字), f1 的局部环境也不会被删除。这是因为返回的匿名函数中存在对其局部环境的引用。
引擎会检查函数退出时,函数外部是否至少存在一个对该函数局部环境的引用 。如果存在,则该局部环境不会被垃圾回收 。
