一、执行环境execution context
定义:
执行环境定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象(VO),环境中定义的所有变量和函数都保存在这个对象中。
独家解毒:每个函数都有自己的执行环境,当执行进入一个函数时,函数的执行环境就会被推入一个执行环境栈的顶部并获取执行权。当这个函数执行完毕,它的执行环境又从这个栈的顶部被删除,并把执行权并还给之前执行环境。这就是ECMAScript程序中的执行流。
也可以这样解读:当调用一个 JavaScript 函数时,该函数就会进入与该函数相对应的执行环境。如果又调用了另外一个函数,则又会创建一个新的执行环境,并且在函数调用期间执行过程都处于该环境中。当调用的函数返回后,执行过程会返回原始执行环境。因而,运行中的 JavaScript 代码就构成了一个执行环境栈。
当函数被调用时函数的局部环境被创建(函数内的代码执行完毕后,该环境被销毁,同时保存在其中的所有变量和函数定义也随之被销毁)。
全局执行环境
全局执行环境是最外围的一个执行环境。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境知道应用程序退出–例如关闭网页或浏览器—时才会被销毁)
函数内执行环境
当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行后,栈将其环境弹出,把控制权返回给之前的执行环境。
执行环境的建立
创建阶段
发生在函数调用时,但在执行具体代码之前。具体完成创建作用域链、创建变量、函数和参数以及求this(讲this的时候讲)的值 。
在创建执行环境阶段会进行函数的定义,此时处于函数定义期:
函数定义的时候,都会创建一个[[scope]]属性,通这个对象对应的是一个对象的列表,列表中的对象仅能javascript内部访问,没法通过语法访问。
我们定义一全局函数A,那么A函数就创建了一个A的[[scope]]属性。此时,[[scope]]里面只包含了全局对象【Global Object】。
而如果, 我们在A的内部定义一个B函数,那B函数同样会创建一个[[scope]]属性,B的[[scope]]属性包含了两个对象,一个是A的活动对象Activation Object、一个是全局对象,A的活动对象在前面,全局对象排在后面。
简而言之,一个函数的[Scope]属性中对象列表的顺序是上一层函数的Activation Object对象,然后是上上层的,一直到最外层的全局对象。
注意这重要的一点--[[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。
* 变量对象VO
每一个执行环境都对应一个变量对象,在该执行环境中定义的所有变量和函数都存放在其对应的变量对象中。
(1)进入执行上下文时,VO的初始化过程如下:
函数的形参:变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined;
函数声明:变量对象的一个属性,其属性名和属性值都是函数对象创建出来的,如果变量对象已经办好了相同名字的属性,则替换它的值
变量声明:变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名,则不会影响已经存在的属性
(2)执行代码阶段,变量对象中的一些属性undefined值将会确定(VO变成AO)
这里需要说明一下:函数表达式不包含在变量对象之中
function foo(x, y) {
var z = 30;
function bar() {} // 函数声明
(function baz() {}); // 函数表达式
}
foo(10, 20);
* 活动对象AO
未进入执行阶段之前,变量对象中的属性都不能访问!(具体实现的引擎可以访问,执行代码是不能访问的)。但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。所以活动对象实际就是变量对象在真正执行时的另一种形式。
其实就是加了一个锁,函数中的程序未执行的时候(进入上下文阶段)有锁,不能访问,而进入执行阶段则可以访问,变量对象变为活动对象。
*作用域对象[[scope]]
函数定义时创建的一个包含函数变量方法的可访问权限的特殊对象
* 作用域链scope chains
作用域链的原理和原型链很类似,如果这个变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层
可访问变量或方法scope chains= 该执行环境的AO + [[Scope]]
var x = 10;
(function foo() {
var y = 20;
(function bar() {
var z = 30;
// "x"和"y"是自由变量
// 会在作用域链的下一个对象中找到(函数”bar”的互动对象之后)
console.log(x + y + z);
})();
})();
执行阶段
主要完成变量赋值、函数引用和解释/执行其他代码
总的来说可以将执行上下文看作是一个对象
EC(execution context) = {
VO:{/*函数中的arguments对象、参数、内部变量以及函数声明*/}
this:{},
Scope:{/*AO以及所有父执行上下文中的AO*/}
}
完整Demo:
// 第一步页面载入创全局执行环境global executing context和全局活动象
// 定义全局[[scope]],只含有Window对象
// 扫描全局的定义变量及函数对象:color【undefined】、changecolor【FD创建changecolor的[[scope]],
// 此时里面 只含有全局活动对象】,加入到window中,所以全局变量和全局函数对象都是做为window的属性定义的。
// 程序已经定义好所以在此执行环境内任何位置都可以执行changecolor(),color也已经被定义,但是它的值是undefined
// 第二步color赋值"blue"
var color = "blue";
// 它是不需要赋值的,它就是引用本身
function changecolor() {
// 第四步进入changecolor的执行环境
// 创建活动对象,扫描定义变量和定义函数,anothercolor【undefined】和swapcolors【FD创建swapcolors的[[scope]]加入changecolor的活动对象和全局活动对象】加入到活动对象,活动对象中同时还要加入arguments和this
// 活动对象推入scope chain 顶端
// 程序已经定义好所以在此执行环境内任何位置都可以执行swapcolors(),anothercolor也已经被定义好,但它的值是undefined
// 第五anothercolor赋值"red"
var anothercolor = "red";
// 它是不需要赋值的,它就是引用本身
function swapcolors() {
// 第七步进入swapcolors的执行环境,创建它的活动对象
// 复制swapcolors的[[scope]]到scope chain
// 扫描定义变量和定义函数对象,活动对象中加入变量tempcolor【undefined】以及arguments和this
// 活动对象推入scope chain 顶端
// 第八步tempcolor赋值anothercolor,anothercolor和color会沿着scope chain被查到,并继续往下执行
var tempcolor = anothercolor;
anothercolor = color;
color = tempcolor;
}
// 第六步执行swapcolors,进入其执行环境
swapcolors();
}
// 第三步执行changecolor,进入其执行环境
changecolor();