为什么出现上下文
js引擎是一段一段代码来执行的,但不一定是按代码顺序来执行代码的。另外js引擎可执行的代码有全局代码、函数代码、eval代码。当执行一个函数时,会做一些准备工作,就叫做执行上下文。
执行上下文栈
js引擎创建了执行上下文栈(ECS)来管理执行上下文
模拟执行上下文执行过程,设执行上下文为一个数组
ECStack=[]
js 引擎要解释代码时先遇到全局代码,所以初始化时先向执行上下文栈压入一个全局执行上下文(gobalContext),只有全部执行完才清空ECStack,所有代码执行完之前执行上下文栈里面始终有gobalContext
ECStack=[gobalContext]
执行一个函数之前会创建一个执行上下文并将它压入执行上下文栈,当函数执行完毕再从执行上下文栈中弹出
比如对下面这段代码:
function f3(){
f2()
}
function f2(){
f1()
}
function f1(){
console.log(123)
}
过程:
//f3()
ECStack.push(<f3> functionContext);
//f3又调用f2
ECStack.push(<f2> functionContext);
//f2又调用f1
ECStack.push(<f1> functionContext);
//f1执行完毕
ECStack.pop();
//f2执行完毕
ECStack.pop();
//f3执行完毕
ECStack.pop();
执行上下文
每个执行上下文包括三个重要属性:
- 变量对象(VO):存储上下文中的变量和函数声明,分为全局上下文的变量对象(就是全局对象window)和函数上下文的变量对象(用AO来表示)
AO是由argument
来创建
AO包括:
1.函数的所有形参:
没有实参,属性值就为undefined
2.函数声明:
若变量对象存在相同名称的属性,替换
3.变量声明:
属性值就为undefined
如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
例如:
function foo(a){
var b=2
function c() {}
var d = function() {};
b = 3;
}
foo(1)
进入执行上下文之后,AO:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
代码执行完之后的AO:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
- 作用域链
- this:在全局执行上下文中,this指向全局对象window,在函数上下文中,this指向调用函数的实例
函数创建
函数里有一个属性[[scope]]来保存所有父变量对象
例如:
function foo() {
function bar() {
...
}
}
函数创建时,各自的[[scope]]:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
函数激活
当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。
执行上下文时变量对象和作用域链怎么工作的
例子:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
执行过程如下:
1.checkscope 函数被创建,保存作用域链到内部属性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
];
3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],//checkscope.[[scope]]即globalContext.VO
}
4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
5.第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
参考文章:
https://github.com/mqyqingfeng/Blog/issues/4
https://github.com/mqyqingfeng/Blog/issues/5
https://github.com/mqyqingfeng/Blog/issues/6
https://github.com/mqyqingfeng/Blog/issues/8