# 词法环境和闭包
# 词法环境
每个代码块{...},脚本,函数,都有一个词法环境的内部关联对象
这个词法环境对象的内部有两个东西
环境记录 :存储了全部的局部变量(包括一些其他信息 this的值)的对象
外部环境的引用
# 1.变量
变量是 环境记录 的一个属性。获取或者修改变量相当于 获取或者修改了词法环境对象 的一个属性
举例
上面的例子只有一个词法环境, 框内表示环境记录对象,箭头表示外部引用(由于没有外部引用,全局词法环境没有外部引用所以是null)
这跟原型链很像是不是!Object的原型指向null
下面继续
先申明一个变量 右边的词法环境变化
- 脚本开始 词法环境预先填充所有声明的变量
- 最初他们处于未初始化状态(Uninitialized)是一种特殊的内部状态,引擎知道存在,但是在let声明之前不能引用,就像变量不存在一样。
- let phrase定义出现,这时候未被赋值,
- 赋值为Hello
- 值被修改
现在来说
- 变量是词法环境的一个属性,与当前执行的函数,代码块,脚本有关系
- 操作变量是操作对象的属性
“词法环境”是一个规范对象(specification object):它仅仅是存在于 编程语言规范 (opens new window) 中的“理论上”存在的,用于描述事物如何运作的对象。我们无法在代码中获取该对象并直接对其进行操作。
但 JavaScript 引擎同样可以优化它,比如清除未被使用的变量以节省内存和执行其他内部技巧等,但显性行为应该是和上述的无差。
# 2.函数声明
一个函数也是一个值,就像变量一样。
不同的是函数在声明后立刻完成初始化
当创建一个词法环境的时候,函数声明会立即初始化完成, 而不是像变量一样要let声明后才能用,所以我们可以再函数声明前调用函数。
例如,这是添加一个函数时全局词法环境的初始状态:
这种只适用于函数声明,不适用于将函数赋值给变量例如
let say = function(name)...
# 3.内部和外部的词法环境
函数在运行的时候,会自动创建一个新的词法环境,存储调用的局部变量和参数
例如,对于 say("John")
,它看起来像这样(当前执行位置在箭头标记的那一行上):
在函数运行的时候,我们得到两个词法环境: 一个是函数内部的词法环境,一个是全局词法环境
可以看到图中的竖线|分割开(左边是函数内部词法环境)(右边是外部全局词法环境)
- 内部词法环境与
say
当前执行相对应,有一个局部变量name
作为当前词法环境的一个属性,调用的是say(John)
所以name
的值是John - 外部词法环境是全局词法环境,包含了phrase变量和say函数
内部词法环境引用了外部环境
当代码要访问一个变量时 —— 首先会搜索内部词法环境,然后搜索外部环境,然后搜索更外部的环境,以此类推,直到全局词法环境。
在任何地方都找不到的话,严格模式会报错。非严格模式会在全局创建一个变量 上面例子搜索过程如下
name
在内部词法环境查找,在alert
试图访问name
时可以找到- 在试图访问
phrase
时发现当前词法环境没有,顺着外部词法环境找到
# 4.返回函数
看到这个例子
function makeCounter() {
let count = 0;
return function() {
return count++;、
};
}
let counter = makeCounter();
在调用makeCounter()
开始,都会创建一个新的词法环境,存储makeCounter
运行是的变量
不同的是在执行makeCounter()
时只是创建了一个嵌套函数:return count++
,这时没有运行,只是创建
所有函数在“出生”时都会记住创建他们的词法环境,不是什么魔法,每个函数都有[[Enviroment]]
隐藏属性,保存了对创建该函数的词法环境的引用。
所以
cunter.[[Enviroment]]
有对count:0
词法环境的引用。这就是函数记住他创建于何处的方式,与函数在哪调用无关。[[Environment]]
引用在函数创建的时候就被永久保存了
等会在调用counter()
时,会创构建一个新的词法环境,并且其外部词法环境引用于counter().[[Environment]]
现在,调用counter()
时查找count变量,首先查找自己的词法环境,没有找到(因为没有局部变量),向上找外部的词法环境(makeCounter()
的词法环境),找到然后修改。
在变量所在的词法环境中更新变量。
下面是执行后的状态
如果我们调用 counter()
多次,count
变量将在同一位置增加到 2
,3
等。
# 闭包
开发者通常应该都知道“闭包”这个通用的编程术语。
闭包 (opens new window) 是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函数被返回(寿命终结)了之后。在某些编程语言中,这是不可能的,或者应该以特殊的方式编写函数来实现。但是如上所述,在 JavaScript 中,所有函数都是天生闭包的(只有一个例外,将在 "new Function" 语法 (opens new window) 中讲到)。
也就是说:JavaScript 中的函数会自动通过隐藏的
[[Environment]]
属性记住创建它们的位置,所以它们都可以访问外部变量。在面试时,前端开发者通常会被问到“什么是闭包?”,正确的回答应该是闭包的定义,并解释清楚为什么 JavaScript 中的所有函数都是闭包的,以及可能的关于
[[Environment]]
属性和词法环境原理的技术细节。
# 参考文献
https://zh.javascript.info/closure#ci-fa-huan-jing