前言
在《执行环境》文中说到,当 JavaScript 代码执行一段可执行代码时,会创建对应的执行上下文 (execution context).
变量对象 (Variable object,VO)
作用域链 (Scope chain)
this
词法作用域
在《作用域》中说到 JavaScript 采用词法作用域也叫静态作用域,这个作用域是在函数编译(js 执行前很短的时间内编译)时决定的,而不是函数调用时决定;
这是因为函数(一个 function 是 Function 的一个实例,这个后面会写到)有一个内部属性 [[scope]],当函数编译时,就会把所有父变量对象保存在内部属性 [[scope]] 中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
注意:虽然 js 是浏览器解释执行,但是 js 也是存在编译(计算机只认识二进制哪里认识你写的 abcd),只是跟 java .net 等语言有点区别,具体可以查看《你不知道 javadcript》,上卷,词法作用域这个本上也有详细解释;
作用域链
在《引出作用域链》中说到作用域链本质是一个指向变量对象的指针链表.
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级 (词法层面上的父级) 执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象.这样由多个执行上下文的变量对象构成的链表就叫做作用域链.
下面以一个例子看看作用域链是怎么构建的:
编译之后函数内部属性
var a=10;
function run(){
var name='Joel';
function say(){
var content='hello',name=' Word';
console.log(content+name+','+a);
}
say();
}
run();//hello Word,10
执行函数
//编译时各自的[[scope]]
run.[[scope]] = [
globalContext.VO //全局变量对象
];
say.[[scope]] = [
run.VO,
globalContext.VO
];
函数执行分为两部分
创建上下文对象
代码执行
创建上下文对象,创建 vo 变量对象,scope chain 作用域链,this 指针以及把函数对象内部属性 [[scope]] 的值复制给上下文对象 scope chain 属性
执行阶段此时上下文被推入环境栈,VO 激活为 AO,此时 VO 已经初始化完成,此时把当前环境的 AO 被插入到 scope chain 顶端
run.ec = {
VO: {
//变量对象初始化
},
//scope chain :run.[[scope]],
scope chain: globalContext.VO,
this: thisValue
}
即 Scope = AO+[[Scope]]
AO 会添加在作用域链的最前面
Scope = [AO].concat([[Scope]])
函数开始执行阶段
作用域链 = (动) 活动对象 (AO) + (静) scope 属性 动指的是执行的时候的变量对象,静指的是词法作用域,即父级变量对象;
//执行
run.ec = {
AO: {
//变量对象初始化
},
// scope chain:AO+run.[[scope]],
scope chain: AO + globalContext.VO,
this: thisValue
}
创建过程
以下面的例子为例,结合着之前讲的执行上下文,变量对象,执行上下文栈,来总结下函数执行上下文中作用域链的创建过程:
执行过程如下:
var scope = "global scope";
function checkscope() {
var scope2 = 'local scope';
return scope2;
}
checkscope();
1.checkscope 函数被创建 / 编译,保存父级变量对象到内部属性 [[scope]]
2. 执行 checkscope 函数,此时并不立刻执行,js 内部开始做准备工作,创建上下文对象 ,推入执行环境栈
checkscope.[[scope]] = [
globalContext.VO
];
checkscopeContext = {}
第一步:创建上下文对象的作用域链:复制函数内部属性 [[scope]] 来创建作用域链
第二步:创建上下文变量对象:一开始只是用 arguments 来初始化变量对象,值为默认值 undefined,继续初始化 function 函数,var 变量
checkscopeContext = {
Scope: checkscope. [[scope]],
}
第三步:绑定 this 指针 变量对象初始化完成,开始执行函数,此时 VO 激活为 AO
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope. [[scope]],
}
3. 准备工作完成,开始执行函数
执行 checkscope 函数,checkscope 函数执行上下文对象被压入执行上下文栈
4.VO 激活成 AO,将活动对象压入 checkscope 作用域链顶端
ECStack = [
checkscopeContext,
globalContext
];
5. 随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6. 查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
总结
ECStack = [
globalContext
];
分析代码的时候,务必回看函数的定义,毕竟是词法作用域.
函数作用域链 = (动) 活动对象 (AO) + (静)scope 属性.
来源: http://www.bubuko.com/infodetail-2455213.html