作用域与作用域链
首先先看一个例子:
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);
先来看以下,js底层代码执行的一个顺序:
ECstack = [ //执行栈
EC(G)={ //创建全局执行上下文
VO(G):{ //全局变量对象
... //包含全局对象原有的属性
x=1;
A=function(y){...} //创建函数的时候就确定了其作用域 (重点)
A[[scope]] = VO(G);
}
}
]
注:我们不管在哪一个执行上下文当中,除了创建变量或者堆以外,同时给当前的函数,声明了它所在的作用域是谁。
看上方例子,函数A在全局创建的,那么A的作用域就是全局的执行上下文。当A执行的时候,就会生成一个新的执行上下文(每个函数执行都会生成一个私有的执行上下文)。
此时,在A的执行上下文,我又创建了一个B函数。那么B也会生成一个私有的执行上下文,同时B的作用域也会被创建,那么B是在A的执行上下文创建的,那么B的函数作用域就是,当前这个A的执行上下文。
函数在执行上下文,除了获取arguments(实参对象),生成作用域外,还做了一件事,生成this指向
函数执行,有个执行主体,进来的第一件事情就是声明this指向,this指向后面说。
除此之外,它还初始化了它的链表,也就是我们常说的作用域链。作用域链的查找规则,就是上面加粗的文字。
下面总结一下:
函数的执行机制:
创建函数的时候:
(1)创建一个堆(存储代码字符串和对应的键值对)
(2)初始化了当前函数的作用域[[scope]]
作用域[[scope]]指的是所在上下文EC中的变量对象VO(变量对象)/AO(函数内的变量对象)
严格来说的话。作用域[[scope]] 与执行上下文是有区别的,作用域指的是当前上下文具体的存放变量的地方,上下文是既可以存变量,又能压缩代码放到执行栈执行的。但是,编程中,我们认为他们俩没啥区别,其实也是不会出错的。
函数执行的时候
创建一个新的执行上下文EC,并且压缩至栈(EC stack)里执行。
初始化this指向
初始化作用域链
创建AO变量对象存储变量
变量又会进行 arguments(它是函数运行时的实参对象) =》 形参 --》如果有变量提升,将变量提升,如果没有变量提升,会将代码执行。
下面看一下,上面题闭包的形成,以及作用域链的查找方式
重:
创建函数的时候,形成的是作用域,以及作用域是谁,函数执行的时候,一定会先初始化好它的作用域链,代码执行当中,遇到一个变量,首先看看它是不是私有变量,如果是,用自己的,如果不是,就会沿着作用域链,一级一级往上查找,一直查找到全局位置,找不到就会报错。这就是闭包,作用域链的一个查找过程。