javascript代码在执行时,会进入一个执行上下文中,执行上下文可以理解为当前代码的运行环境。
> 1 全局环境:代码运行起来首先会进入全局环境
> 2 函数环境:当函数被调用执行时,会进入当前函数中执行代码
> 3 eval函数环境:不建议使用,这里不做介绍。
所以在一个javascript程序中,必定会出现多种不同的执行上下文。javascript是一个单线程语言,这意味着在浏览器中同时只能做一件事情。当javascript解释器初始执行代码,它首先默认进入全局上下文。每次调用一个函数将会创建一个新的执行上下文。每次新创建的一个执行上下文会被添加到作用域链的顶部,有时也称为执行或调用栈。浏览器总是运行位于作用域链顶部的当前执行上下文。一旦完成,当前执行上下文将从栈顶被移除并且将控制权归还给之前的执行上下文。
不同执行上下文之间的变量命名冲突通过攀爬作用域链解决,从局部直到全局。这意味着具有相同名称的局部变量在作用域链中有更高的优先级。简单的说,每次你试图访问函数执行上下文中的变量时,查找进程总是从自己的变量对象开始。如果在自己的变量对象中没发现要查找的变量,继续搜索作用域链。它将攀爬作用域链检查每一个执行上下文的变量对象,寻找和变量名称匹配的值。
我们现在已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:
>建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)
- - - 建立变量,函数,arguments对象,参数
- - - 建立作用域链
- - - 确定this的值
代码执行阶段:
- - - 变量赋值,函数引用,执行其它代码
确 切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我 上述所描述的两个阶段中的第一个阶段 - 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象 (executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。
上述第一个阶段的具体过程如下:
找到当前上下文中的调用函数的代码
在执行被调用的函数体中的代码以前,开始创建执行上下文
建立variableObject对象:
建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
检查当前上下文中的函数声明:
每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
检查当前上下文中的变量声明:
每找到一个变量的声明,就在variableObject下,用变量名建立一个属性,属性值为undefined。
如果该变量名已经存在于variableObject属性中,直接跳过(防止指向函数的属性的值被变量属性覆盖为undefined),原属性值不会被修改。
确定上下文中this的指向对象
执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。
「实例1」
var color = "blue";
function changeColor(){
var anotherColor = "red";
function swapColor() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColor();
}
changeColor();
我们用ECStack来表示处理执行上下文的堆栈 第一步全局上下文入栈,并一直存在于栈底,如图所示:
第二步,全局上下文入栈之后,从可执行代码开始执行,直达遇到changeColor(),这句代码激活了函数changeColor,从而创建changeColor自己的执行上下文,因此此时是changeColor ECStack的上下文入栈。
第三步,changeColor EC 的上下文入栈之后,开始执行其中的可执行代码,并在遇到swapColor()这句代码之后激活swapColor()执行上下文,因此第三步就是swapColor EC的上下文入栈
第四步,在swapColor的可执行代码中,没有其他能生成执行上下文的情况,因此这段代码顺利执行完毕,swapColor的执行上下文入栈中弹出,如图所示
第五步,swapColor的执行上下文弹出之后,继续执行changeColor的可执行代码,遇到其他执行上下文,顺利执行完毕后弹出,这样ECStack就剩下全局执行上下文了,如图所示
最后,,全局执行上下文在浏览器关闭后出栈。需要注意的是,函数执行上下文遇到 return 能直接终止可执行代码的执行,因此会直接将当前上下文弹出栈。
「实例2」
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result();
这是一个闭包的例子,整个例子有一定的迷惑性 但是我们只需要根据“函数执行才会创建执行上下文”这一个原则来理解,那么这段代码执行时的函数调用栈顺序就会比较清晰了。第一步,仍然是全局上下文先入栈,如图所示
第二步,就是全局代码在执行过程中,遇到了f1()函数,执行var result = f1();,因此f1会创建对应的执行上下文并入栈,
第三步,在f1的可执行代码中,虽然声明了一个函数f2,但是并没有执行任何函数,因此也就不会产生别的执行上下文,代码执行结束后,f1自然会出栈,如图所示
第四步,f1出栈之后,继续执行全局上下文的代码,这个时候遇到了result(),result()函数会创建一个新的执行上下文,因此这个时候result的上下文入栈,如图
第五步,这个result()其实就是在f1中声明的函数f2,因此这个时候就会执行f2的代码,由于f2中没有产生新的执行上下文,因此执行完毕后直接出栈