概念
执行环境(execution context):是JavaScript中一个非常重要的概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
执行环境的变量对象:每个执行环境都有一个对应的变量对象,它用于存储环境中定义的所有变量和函数。我们在代码中无法直接访问它,但解析器在后台会实际使用它。
作用域链(scope chain):当函数被执行时,它的执行环境会被推入一个环境栈中。在函数被执行完毕后,会从栈中弹出,将控制权交给栈顶的执行环境,栈底是全局执行环境。当我们查询标识符的作用时,采用就近原则,从栈顶向栈底查找,这就是作用域链。
执行环境
- 全局执行环境
全局执行环境根据宿主的不同,会有变化。
众所周知,JavaScript实际上是ECMAScript以Web浏览器为宿主后的称谓。一个简单的公式如下:
JavaScript = ECMAScript + BOM + DOM
所以,JavaScript的所有全局变量和函数,都是作为window对象的属性创建的。
function my_global_func(){
console.log(123)
}
var test_var = 'hello'
console.log(window.test_var) // hello
console.log(window.my_global_func) // ƒ (){console.log(123)}
- 局部执行环境
每个函数都有自己的局部执行环境,生成一个“变量对象”,函数内定义的所有变量和函数,都会作为“变量对象”的属性存在。
var color = 'blue'
function changeColor(){
color = 'red'
}
changeColor()
如上面这段代码所示,存在一个全局执行环境和一个局部执行环境,该局部执行环境由函数“changeColor”生成。
在此段代码片段中,函数“changeColor”的局部执行环境中,并没有定义任何变量,因此该局部执行环境中只有一个默认的变量: arguments。
- arguments
在任何函数中,都存在一个默认的无需定义的变量:arguments,它以数组的形式存储函数的入参。
也可以认为,它是函数执行环境中,“变量对象”的默认属性,值得注意的是,全局执行环境没有arguments属性。
var test = function(){
console.log(arguments)
}
test('1','2') //['1','2']
console.log(arguments) // 报错
arguments除了包含函数入参,拥有数组的方法以外,还拥有一个callee方法,这个方法的具体用法,后面再讲。
作用域
作用域:一个函数/变量可以被使用的作用范围,被称为作用域。
在JAVA、C、C++ 等开发语言当中,变量/函数 的作用域以块为单位。但在JavaScript这门语言当中,却不是以块为单位进行作用域的判断的。
if(true){
var color = 'blue'
}
console.log(color) // blue
在存在块级作用域的语言中,以上代码会报错,但JavaScript却能够打印出在if块中定义的变量的值。原因在于JavaScript中var关键字定义的变量存在“变量”提升这个过程,在编译的过程中,实际执行过程如下:
var color = undefined
if(true){
color = 'blue'
}
console.log(color) // blue
而变量提升的本质,是提前给执行环境中的“变量对象”设置属性值,而局部执行环境已函数为单位,因此变量提升的过程也只到函数这一层。
那么,在一个函数内,是否可以访问其执行环境以外的变量呢?
答案当然是肯定的。
var color = 'blue'
function test(){
console.log(color)
}
test() // blue
通过上面这个简单的例子,我们可以发现,在函数的局部执行环境中,可以访问全局执行环境中的变量。
var color = 'blue'
function test(){
var color02 = 'red'
var test02 = function(){
console.log(color)
console.log(color02)
}
test02()
}
test() // blue red
通过上面这个例子,我们可以发现,在函数的局部执行环境中,不仅可以访问全局执行环境的变量,还可以访问上一层函数中定义的变量。
实际上:在一个函数被执行时,它被推入一个环境栈,最上层永远是执行中的函数的执行环境,其次为该函数的上一层函数的执行环境,以此类推,底层为全局执行环境。
当编译器寻找标识符时,会从栈顶向栈底依次搜寻,一旦找到符合条件的标识符便结束此次搜寻,如果编译器未能找到该标识符,便会报错。