小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
提到变量提升,这些知识,需要你提前知晓~
执行上下文(Execution Context),又叫执行环境。解释器遇到可执行代码时,就会进入一个执行上下文,可以理解为当前代码的执行环境。
执行上下文有三种分类:
-
全局执行上下文 -
函数执行上下文 -
eval函数执行上下文(很少用,忽略)
在JavaScript执行过程中, 全局执行上下文(global excution context)只有一个,在全局执行上下文中,会创建一个全局window对象(浏览器的情况下)并设置this的值为这个全局对象。
而函数执行上下文可以有多个。每当函数被调用时,就会创建一个新的函数执行上下文,这些执行上下文最后构成一个执行上下文栈(Excution context stack,ECS)来管理执行顺序。因为js是单线程的,所以只有当栈顶执行完出栈了,才能执行下一个上下文。
举个例子:
程序第一个进入的总是默认的全局执行上下文,所以说它总在ECS的底部,接着是函数执行上下文fn1、fn2,如图所示:
每个Excution Context有三个属性,变量对象(Variable object,VO),作用域链(Scope chain)和this。如下所示:
变量对象(Variable Object,VO),里面都有些啥?var声明的变量,声明的函数和函数的形参。有两种特殊情况不在VO中,1、函数表达式不在VO中;2、不是var声明的变量不在VO中,这种变量相当于给window添加了一个属性。
活动对象(Activation object),在函数执行上下文中,VO是不能直接访问的,此时由激活对象(Activation Object,缩写为AO)扮演VO的角色。激活对象 是在进入函数上下文时刻被创建的。
AO其实就是VO,只是他俩处于执行上下文的不同生命周期而已。
一个执行上下文的声明周期可以分为两个阶段。1、创建阶段,创建变量对象、作用域链以及设置this的值。2、激活/执行代码阶段,完成变量的赋值,函数的引用以及执行其他的代码。
下面让我们来举个例子,加深印象吧。
function fn1() {
var a = 1;
var b = 2;
var fn2 = function(){
}
}
fn1();
创建阶段(用伪代码的形式展示):
fn1ExcutionContext = {
variableObject:{
a:undefined,
b:undefined,
fn2:undefined
}
scopeChain:{...},
this:{...}
}
激活/执行阶段,代码更新为:
fn1ExcutionContext = {
ActiveObject:{
arguments: {...},
a:1,
b:2,
fn2:<fn2 reference>
}
scopeChain:{...},
this:{...}
}
小tips:
console.log(fn1);
function fn1() {
}
var fn1 = 1;
这段代码你认为会输出什么呢?如果你认为是1,那就错啦。注意,函数的声明提前优先级高于变量声明提前哦。 读到这里,是不是觉得变量提升问题就是小菜一碟。