命名空间、词法环境、闭包
Willem Zhang Lv6

start

闭包

1
2
3
4
5
6
7
8
(function() {
// 函数创建一个命名空间

window.foo = function() {
// 对外公开的函数,创建了闭包
};

})(); // 立即执行此匿名函数

闭包创建了命名空间(即词法环境),通过此方法可以避免在全局作用域(全局命名空间、全局词法环境)声明同一变量名引发的冲突。

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function() {
// 函数创建一个命名空间
a = 1;
window.foo = function() {
console.log(a)// 对外公开的函数,创建了闭包
};

})(); // 立即执行此匿名函数

(function() {
// 函数创建一个命名空间
a = 2;
window.foo2 = function() {
console.log(a)// 对外公开的函数,创建了闭包
};

})();

foo() //返回1
foo1() //返回2

通过两个即时执行函数创建了两个命名空间(词法环境),在这两个词法环境定义相同的变量名,不会引发变量名冲突。
同时,在执行window.foo2 = function() {};时,生成了一个闭包。
等号右边的函数一经定义,该匿名函数就会记录自己在哪里出生的,即拥有一个对创建它的词法环境的引用。而由于有这个引用,导致了即使即时执行函数执行完也不会使该词法环境被gc回收。

以上过程可以理解为:

  1. 声明函数创建局部词法环境
  2. 通过在函数中定义子函数从而使函数的词法环境保持被引用的状态,使得即使函数执行完毕其词法环境也不会被gc回收。(这个过程就是创建闭包的过程)

闭包:通过在一个函数内部定义子函数的方式使得这个函数的词法环境保持被引用的状态从而不会被gc回收。定义闭包不一定要return这个子函数或者给外部作用域赋值这个函数,即使只是声明子函数也会产生闭包,但是由于不能调用这个子函数所以不能访问到父函数词法环境。fun(1)(2)是先执行完fun(1)然后返回一个子函数,子函数由于引用父函数词法环境所以父函数词法环境不会消失,使得子函数可以访问到父函数的词法环境。

块级作用域和函数作用域的实现依托于执行上下文。let声明的变量不会提升,是因为在函数执行上下文中将所有用let变量声明的变量名记录到词法环境的环境记录ER中,值为uninitialized,所以在let声明前调用这个变量会报出没有初始化的错误,块级作用域得以实现。,(这跟作用域没有关系,实现的是用let声明的变量不会被提升,而块级作用域的实现是通过环境记录内部的栈实现的,进入块将与块外面的变量名同名的变量名的值压入栈中,出块将这个值弹出)当在函数中用var声明变量时,函数执行上下文会将函数中的所有用var声明的变量名记录到变量环境中,并赋值为undefined,此时在var声明前访问这个变量会得到undefined值,这与var声明后不赋值然后访问的结果是相同的,于是就实现了hoisting,变量声明提升,而变量赋值不提升的效果。

  • 执行上下文 (分为全局的和函数的,也有eval的)
    • 词法环境
      • 环境记录RC
        • 声明式环境记录(函数作用域,存储变量、函数、参数)
        • 对象式环境记录 (全局作用域和块级作用域,存储变量、函数)
      • 外部环境引用outer
    • 变量环境(只保存用var声明的变量,并赋值为undefined)
    • this绑定

let、const声明的变量,外部环境引用保存在词法环境组件中。
var和function声明的变量和保存在环境变量组件中。

函数内部有一个子函数,函数被执行时,首先执行上下文进行解析阶段,在该阶段扫描函数内代码,会把子函数名记录到函数词法环境的环境记录中,并记录这个子函数的具体内容。这个子函数只是被定义了,还没有被调用。但是在这个时候这个子函数就会有一个[[scope]]属性指向自己被创建时所处的词法环境。当这个子函数被调用的时候,就会生成这个子函数的执行上下文,并且其词法环境的环境记录里有对外部环境的引用。

块级作用域不存在编译过程,词法环境是一个小型的栈,所以块级作用域执行是将变量加入词法环境栈中

词法环境是规则,不代表具体的实现方法

一个执行上下文的生命周期可以分为两个阶段。
创建阶段和代码执行阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
let a = 1;
const b = 2;
var c = 3;
function test (d, e) {
var f = 10;
return f * d * e;
}
c = test(a, b);

// 执行上下文生成分为解析阶段和执行阶段
// 全局环境的解析阶段
GlobalLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: null,
EnviromentRecord: {
Type: 'object',
a: <uninitialized> ,
b: <uninitialized>
},
},
VariableEnvironment: {
EnviromentRecord: {
type: 'object',
test: <func>,
c: undefined,
}
}
}

// 要开始执行还没执行test函数时的函数环境解析阶段
GlobalLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: null,
EnviromentRecord: {
Type: 'object',
a: 1 ,
b: 2
},
},
VariableEnvironment: {
EnviromentRecord: {
type: 'object',
c: 3,,
test: <func>
}
}
}

FunctionLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: <GlobalLexicalEnvironment>,
EnviromentRecord: {
Type: 'Declarative',
arguments: {0: 1, 1: 2, length: 2}
},
},
VariableEnvironment: {
EnviromentRecord: {
Type: 'Declarative',
f: undefined,
}
}
}

// 执行完毕后的词法环境和变量环境
GlobalLexicalEnvironment = {
LexicalEnvironment: {
OuterReference: null,
EnviromentRecord: {
Type: 'object',
a: 1 ,
b: 2 ,
test: <func>
},
},
VariableEnvironment: {
EnviromentRecord: {
type: 'object',
c: 20,
}
}
}

FunctionLexicalEnvironment = {,
LexicalEnvironment: {
OuterReference: <GlobalLexicalEnvironment>,
EnviromentRecord: {
Type: 'Declarative',
arguments: {0: 1, 1: 2, length: 2}
},
},
VariableEnvironment: {
EnviromentRecord: {
Type: 'Declarative',
f:10,
}
}
}


ref

命名空间

词法环境

执行上下文、词法环境、变量环境

「变量提升」和「块级作用域」主要依托于执行上下文。 这句话正确且给予启发,然而链接的内容很大一部分是错误的

javascript的执行环境、词法环境、变量环境
非常清晰 有对象代码 es5内容

执行上下文和执行环境是一个东西
es3内容

重学js —— Lexical Environments(词法环境)和 Environment Records(环境记录)

js 图解变量环境、词法环境、执行上下文,作用域链查找过程

理解Javascript中的执行上下文和执行栈

原文

作用域、作用域链

DynamicBindingVsLexicalBinding

Dynamic and Lexical variables in Common Lisp

[JavaScript] Javascript 的作用域 (Scope) 與範圍鏈 (Scope Chain):往外找
静态作用域:对代码进行编译时,根据代码的写法,作用域就已经决定好了
动态:直到执行到那个位置才能决定作用域
Lexical Environment 和 Lexical Scope 之間最大的差異,就是 Lexical Environment 是在程式執行中存放環境資訊的地方,而 Lexical Scope 則為在程式編譯時就已經決定好的作用域。

ECMA

https://segmentfault.com/a/1190000039854311
清晰

JS:深入理解JavaScript-词法环境
格式很好 内容很清晰
没有块级的词法环境,执行到了块不会创建块级上下文 只是在函数词法环境初始化的时候不会把块里面的用let和const声明的变量进行binding,只有当执行到了块才开始将块里面的用let和const声明的变量存入一个栈中,等到出了块级作用域这个块中声明的变量的值会被弹出。

1. 彻底搞懂javascript-词法环境(Lexical Environments)
户口登记制度

词法作用域

  • Post title:命名空间、词法环境、闭包
  • Post author:Willem Zhang
  • Create time:2021-11-21 12:18:15
  • Post link:https://ataraxia.top/2021/11/21/命名空间、词法环境、闭包/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
 Comments