2020年5月23日
let x = 1;
function A(y){
let x = 2;
function B(z){
console.log(x+y+z);
}
return B;
}
let C = A(2);
C(3);
认识一个「闭包」概念,需要有ECStack、EC(G)、EC(函数)、VO、GO、AO等这样的概念作为铺垫……
关键点:
A
函数的作用域就是 VO(G)
-> 说白了,函数在哪里声明创建的,那么这个哪里就是它的作用域,如 A
是在全局创建的,那么A
的作用域就是全局上下文一个函数从创建到执行的这么一个大概过程:
[[scope]] = VO/AO
this
的指向[[scopeChain]]: AO(A) -> VO(G)
(根据该函数 init 作用域时,确定的 VO(G)/AO
)老师的参考来源:官方文档、大神博客、标准、V8源码、还有其它很多很多的素材 -> 有些人讲的可能不一定对,所以需要对比一下看看大家都怎么说 -> 最后得出自己所认为的「知识」 -> 当然,老师说得有些地方也是不太严谨的,但根据老师所看的,基本就是如上边所述的这样……
知识点:
关于闭包:
函数A入栈的 EC(A)
其实就是闭包,只是生命周期太短了,即函数执行完就销毁了,一般我们都认为这不是闭包,但其实这也算是
如果EC(A)
里边的变量有被上一级作用域引用到,那么这整个EC(A)是不会被销毁的,因为一旦销毁了,那么数据就没了呀!这样上一级作用域就无法读取到了
因此,我们会说这样的 EC(A)
才是闭包
闭包就两种作用:
当然,这样一说,我们可能还是无法理解的,所以老师会在下一节多讲一些关于闭包的实战应用场景 -> 涉及到JS里边的一些高阶编程技巧
关于销毁,在函数里边创建的私有变量,如果函数执行完了,那么这些私有变量都会被GG掉,假如全局作用域的某个变量通过某种途径拿到了私有变量的状态,那么这个函数EC就不会被销毁了
疑问:
作用域链查找:
[[scope]]
属性,表明该函数的作用域是谁上一级的AO/VO
,表明如果当前不是函数的私有变量,那么就会往这个指明的 AO/VO
上找到该变量的值…… -> 直到全局 -> 全局都没找着,那就会报错!/*第一步:创建全局执行上下文,并将其压入ECStack中*/
ECStack = [
//=>全局执行上下文
EC(G) = {
//=>全局变量对象
VO(G):{
... //=>包含全局对象原有的属性
x = 1;
A = function(y){...};
A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
}
}
];
/*第二步:执行函数A(2)*/
ECStack = [
//=>A的执行上下文
EC(A) = {
//=>链表初始化为:AO(A)->VO(G)
[scope]:VO(G)
scopeChain:<AO(A),A[[scope]]>
//=>创建函数A的活动对象
AO(A) : {
arguments:[0:2],
y:2,
x:2,
B:function(z){...},
B[[scope]] = AO(A);
this:window;
}
},
//=>全局执行上下文
EC(G) = {
//=>全局变量对象
VO(G):{
... //=>包含全局对象原有的属性
x = 1;
A = function(y){...};
A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
}
}
];
/*第三步:执行B/C函数 C(3)*/
ECStack = [
//=>B的执行上下文
EC(B){
[scope]:AO(A)
scopeChain:<AO(B),AO(A),B[[scope]]
//=>创建函数B的活动对象
AO(B):{
arguments:[0:3],
z:3,
this:window;
}
},
//=>A的执行上下文
EC(A) = {
//=>链表初始化为:AO(A)->VO(G)
[scope]:VO(G)
scopeChain:<AO(A),A[[scope]]>
//=>创建函数A的活动对象
AO(A) : {
arguments:[0:2],
y:2,
x:2,
B:function(z){...},
B[[scope]] = AO(A);
this:window;
}
},
//=>全局执行上下文
EC(G) = {
//=>全局变量对象
VO(G):{
... //=>包含全局对象原有的属性
x = 1;
A = function(y){...};
A[[scope]] = VO(G); //=>创建函数的时候就确定了其作用域
}
}
];
听说:
都2020年了 怎么还在说变量对象、活动对象、作用域链这些ES3里过时的概念,ES5之后早就变成词法环境了,文章也要与时俱进啊
规范里都删了 VO 和 AO 多久了,现在都是词法环境,环境记录了
不过,这只是术语变了,但本质还是没有变的!
还有关于context的由来:
一般来说是想要有个object来保存状态,想不出好的名字然后就叫context了
题外话(Eval函数也会有EC):
Eval
函数执行上下文 -> 执行在 eval
函数内部的代码也会有它属于自己的执行上下文,但由于 JavaScript 开发者并不经常使用 eval
,所以在这里我不会讨论它。➹:(译)理解 JavaScript 中的执行上下文和执行栈 - 掘金
词法作用域(静态作用域)是在书写代码或者说定义时确定的,而动态作用域是在运行时确定的。 词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用,其作用域链是基于运行时的调用栈的。
JS是词法作用域的
词法作用域:
function foo() {
console.log(a) //2
}
function bar() {
var a = 3
foo()
}
var a = 2
bar()
分析这个过程:
[[scope]] -> VO(G)
[[scope]] -> VO(G)
bar()
EC(bar)
[[scopeChain]] -> VO(G)
{}
foo()
AO
没找到 foo
这个变量 -> VO
找到了foo
[[scopeChain]] -> VO(G)
console.log(a)
-> 是 3 还是 2? -> foo的[[scopeChain]]
指向的是VO(G)
-> 所以是 2
动态作用域:
function foo() {
print a; // 输出 3 而不是 2
}
function bar() {
var a = 3
foo()
}
var a = 2
bar()
有人说「两段代码不是一样的么」?
代码一样结果却不同,才能控制变量法地体现出语言层面上 词法作用域 和 动态作用域 的差别
代码是一样的,解释器对代码的解释不一样,一个用词法作用域规则处理的,一个用动态作用域规则处理的
要是让我遇上第二种,估计会发疯