执行环境在JavaScript中是一个非常重要的概念。执行环境定义了变量或函数访问其他数据的权限和行为,每个执行环境都有一个与之相关联的变量对象。在该执行环境下定义的变量和函数都被保存在这个变量对象中。
执行环境的分类:全局执行环境、局部执行环境
1、全局执行环境
全局执行环境是处于最外围的执行环境。在浏览器中,全局执行环境就是window对象,在全局作用域下定义的变量和函数都是window的属性。执行环境中的所有变量和函数都会在执行环境中的所有代码执行完毕后自动销毁,对于全局执行环境来说就是关闭标签页或浏览器。
2、局部执行环境
每个函数也都有自己的执行环境,在函数内定义的变量和函数都属于这个执行环境。当执行流进入这个执行环境时,会把该执行环境压入一个环境栈中,当该执行环境执行完毕时就会被弹出栈,把控制权交给上一个执行环境。(自行脑补进栈出栈的过程)
作用域链
作用域链的用途,就是保证对该执行环境有权访问的变量或函数 的有序访问。
当一段代码在某执行环境中执行时,会创建一个当前执行环境下的作用域链,该作用域链的前端是当前执行环境的变量对象。如果该执行环境是一个函数,则其活动对象 将会被作为变量对象插入到该作用域链的前端。作用域链是一条不断增长的链,比如在全局执行环境下,作用域链的顶端是window,当执行进一个函数时,在函数内部创建了一个当前执行环境下的作用域链,该作用域链的顶端是该函数的变量对象,然后该作用域链链接到了全局执行环境下的作用域链的顶端,成为新的顶端。
var color="red";
function getColor(){
var otherColor="blue";
function temp(){
var tempColor="black";
color="yellow";
//该执行环境下能访问tempColor、otherColor、color
}
//该执行环境下能访问otherColor、color
alert(color);
}
//该执行环境下只能访问color
getColor();
上述代码有三个执行环境:全局执行环境、getColor()的执行环境和temp()的执行环境。在上述代码执行的过程中一步步创建了相应的作用域链,并且最终成为一个长链。
全局执行环境下,有一个color变量和一个getColor函数,
getColor的执行环境下,有一个otherColor变量和temp函数,
temp的执行环境下,有一个tempColor变量。
所以,作用域链应该是这个样子 :
全局执行环境(color , getColor() ) <------------- getColor()执行环境( otherColor , temp() ) <-------------- temp()执行环境( tempColor ) 顶端!
内部环境可以通过作用域链访问外部环境的变量和函数,而外部环境不可以访问内部环境的变量和函数。
例如:对于temp( )来说,其作用域链包含3个变量对象(全局变量对象、getColor()变量对象、temp( )变量对象)。如果想要在temp( )执行环境内访问color,则首先在自己的变量对象中查询是否有这个color,若没有,则寻着作用域链向上查找,在getColor( )的变量对象中也没有查找到,则继续向上,结果在全局变量对象中找到了color。
延长作用域链
有些语句可以在作用域链的前端临时增加一个变量对象,从而达到延长作用域链的目的,该变量对象会在代码执行完毕后被移除。
1、try--catch中的catch块。在catch块中会创建一个新的变量对象,其中保存的是被抛出的错误对象的声明。
2、with语句。with语句中会创建一个变量对象,with语句接收的对象的所有属性和方法都加入到该变量对象,然后将给变量对象插入到作用域链的前端。
没有块级作用域
在ES5中没有块级作用域,即像在if while for等语句的 { } 中定义的变量属于其最近的执行环境。如果在初始化变量时没有用var,则自动添加到全局执行环境中。
function X(){
color="red";
if(true){
var num=2019;
}
console.log(num); //输出2019
}
X();
console.log(color); //输出red
查询标识符
当在某个执行环境中为了读取或写入而引用一个标识符时,必须通过搜索来确定标识符实际代表什么。搜索过程从作用域链的前端开始,逐级向上查询,如果一直到全局执行环境中都没找到该标识符,则说明该变量未声明。如果在局部执行环境中的某个标识符和全局执行环境中的标识符重名,则搜索会到局部执行环境就截止,不会再搜索全局执行环境中的变量。
因此,在某一执行环境中访问局部变量要比访问全局变量快得多,因为不用进行向上搜索作用域链。