前言
在之前的部分里,我们花费了大量的力气,终于算是学习明白了每个执行上下文的三个重要属性:变量对象(Variable object,VO),作用域链(Scope chain),this,现在我们可以试着结合之前的上下文来更加深入地分析一下代码的执行过程了。
正文内容
抛出问题
copy过来一道现成的题目
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f(); //差别在这里1
}
checkscope(); //差别在这里2
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f; //差别在这里1
}
checkscope()(); //差别在这里2
两段代码都会打印’local scope’。虽然两段代码执行的结果一样,但是两段代码究竟有哪些不同呢?
详细分析
首先分析一下第一段
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
①首先,执行全局代码,创建全局执行上下文,并将全局上下文压入执行上下文栈。
ECStack = [
globalContext
];
②全局上下文初始化
globalContext = {
VO: [global, scope, checkscope],
Scope: [globalContext.VO],
this: globalContext.VO
}
与此同时,checkscope 函数被创建,保存作用域链到函数的内部属性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
③执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
];
④checkscope 函数执行上下文初始化
- 将checkscope 函数的作用域链复制到checkscope 函数上下文的Scope里
checkscopelContext = {
Scope: [globalContext.VO] //将函数作用域链搬到函数执行上下文里
}
- 在函数上下文里用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明,最后将活动对象压入 checkscope 作用域链顶端。
checkscopelContext = {
AO: {
arguments: { //用 arguments 创建活动对象
length: 0
},
scope: undefined //加入形参、函数声明、变量声明
f: reference to function f(){}
}
Scope: [AO, globalContext.VO],//将活动对象压入 checkscope 作用域链顶端
}
⑤ checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
⑥ 执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈
ECStack = [
fContext,
globalContext
];
⑦ 函数f 上下文初始化,与第④步相同(大佬可以跳过,我不是很熟悉再过一遍)
- 首先将函数作用域链复制到上下文里
fContext = {
Scope: [ checkscopeContext.AO, globalContext.VO],
}
- 然后用 argument 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明,最后将活动对象压入 f 作用域链顶端。
fContext = {
AO:{
argument:{
length:0;
}
}
Scope: [AO, checkscopeContext.AO, globalContext.VO],
this: undefined
}
⑧ f 函数执行,沿着作用域链查找 scope 值,返回 scope 值
⑨ f 函数执行完毕,f 函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
这样下来,我们终于可以从原理的角度来解读一段代码了,但是我们依然看不出来这两个例子的不同之处,那就继续学习后面的知识吧。
总结
学习到这里,我们算是学习了较为完整的一部分知识,理解之后能解释一些原来无法理解的执行顺序,让我们开发起来更加得心应手与规范高效。