1.1 执行上下文
当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。 executable code: 1、全局代码 2、函数代码 3、Eval代码
//A
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
//B
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
执行上下文栈(Executable Context Stack)
当 JavaScript 开始要解释执行代码的时候,
1、最先遇到的就是全局代码,所以初始化的时候首先就会向执行上下文栈压入一个全局执行上下文,我们用 globalContext 表示它,并且只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个 globalContext。
2、当执行一个函数的时候,就会创建一个执行上下文,并且压入执行上下文栈,当函数执行完毕的时候,就会将函数的执行上下文从栈中弹出。
//A
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope(); //"local scope"
1、栈底首先压入全局上下文 ECStack.push(GlobalContext);
2、然后checkscope函数执行,将其执行上下文压入栈中 ECStack.push(<checkscope> functionContext);
3、遇到return时,f函数执行 ECStack.push(<f> functionContext);
4、f函数执行完毕,弹出栈 ECStack.pop();
5、checkscope函数执行完毕,弹出栈 ECStack.pop();
6、最后GlobalContext弹出
//B
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()(); //"local scope"
1、栈底首先压入全局上下文 ECStack.push(GlobalContext);
2、然后checkscope函数执行,将其执行上下文压入栈中 ECStack.push(<checkscope> functionContext);
3、checkscope函数执行完毕,return f函数;弹出栈 ECStack.pop();
4、return之后还有一个执行符,f函数执行 ECStack.push(<f> functionContext);
5、f函数执行完毕,弹出栈 ECStack.pop();
6、最后GlobalContext弹出
执行上下文三要素
变量对象Variable Object(VO)
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
1、全局上下文
全局上下文的变量对象就是全局对象
this === Window
2、函数上下文
活动对象(activation object, AO) === 变量对象。
-
AO在进入函数上下文时刻被创建,通过函数的 arguments 属性初始化。
-
函数上下文的变量对象初始化只包括 Arguments 对象,arguments 属性值是 Arguments 对象。
-
在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值
-
在代码执行阶段,会再次修改变量对象的属性值
函数上下文的代码分为分析(进入执行上下文)和执行
分析阶段(函数预编译)
-
预编译提升过,函数执行时就不再看
-
先函数声明整体提升(提升到逻辑最前面)
-
再变量 声明提升(只提升声明,赋值不提升)
-
AO(Activation Object)活动对象
1、创建AO对象(执行期上下文)
2、找形参、变量声明 ,然后赋给AO,值为undefined
3、形参实参相统一(实参值赋给形参)
4、找函数体内 函数声明 ,并将函数体作为值赋给属性名
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
//预编译
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
//执行后
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
作用域链Scope Chain
由多个执行上下文的变量对象构成的链表
当查找变量的时候,会先从当前上下文的变量对象中查找
如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。
这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
函数的作用域在函数定义的时候就决定了。
这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
function foo() {
function bar() {
...
}
}
//函数创建时
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
//当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。
//这时候执行上下文的作用域链,我们命名为 Scope:
Scope = [AO].concat([[Scope]]);
过程
- 全局上下文
- 函数声明提升
- 函数创建 [[scope]] 属性(保存父变量对象/作用域)
- 执行函数,创建函数上下文,压入执行上下文栈
- 复制函数[[scope]]属性创建函数上下文的作用域链
- 活动对象AO 初始化
- 将 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]],
}
//4、第二步:用 arguments 创建活动对象AO,随后初始化活动对象,加入形参、函数声明、变量声明
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
];
例题 (综合)
-
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); } checkscope(); //1、全局代码执行,创建全局上下文,压入执行上下文栈 ECStack = [ globalContext ]; //2、全局上下文初始化 globalContext = { VO: [global], Scope: [globalContext.VO], this: globalContext.VO } //3、函数声明提升,checkscope 函数创建,保存作用域链到函数的内部属性[[scope]] checkscope.[[scope]] = [ globalContext.VO ]; //4、执行 checkscope 函数,创建checkscope函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈 ECStack = [ checkscopeContext, globalContext ]; //5、checkscope 函数执行上下文初始化(AO) -复制函数 [[scope]] 属性创建作用域链, -用 arguments 创建活动对象, -初始化活动对象,即加入形参、函数声明、变量声明, -将活动对象压入 checkscope 作用域链顶端。 同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]] checkscopeContext = { AO: { arguments: { length: 0 }, scope: undefined, f: reference to function f(){} }, Scope: [AO, globalContext.VO], this: undefined } //6、执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈 ECStack = [ fContext, checkscopeContext, globalContext ]; //7、f 函数执行上下文初始化, 以下跟第 5 步相同: fContext = { AO: { arguments: { length: 0 } }, Scope: [AO, checkscopeContext.AO, globalContext.VO], this: undefined } //8、f 函数执行,沿着作用域链查找 scope 值,返回 scope 值 //9、f 函数执行完毕,f 函数上下文从执行上下文栈中弹出 ECStack = [ checkscopeContext, globalContext ]; //10、checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出 ECStack = [ globalContext ];
this
放在后面一篇讲