JS作用域链详解
JavaScript采用词法作用域,也就是变量的作用域在定义时就已经确定了。而在JavaScript中,作用域可以形成一个链式结构,这被称为作用域链。在这个链结构中,每一个函数都有自己的作用域,如果一个变量在当前作用域中未定义,则会沿着作用域链向上查找,直到查找到该变量为止,或者到达全局作用域。
作用域链的构成
JavaScript中的作用域链是由一个个执行上下文(Execution Context)构成的。而每一个执行上下文又分为三个部分:变量对象(Variable Object),作用域链(Scope Chain)和this对象。
变量对象是当前执行上下文中的变量、函数声明和形参的存储空间,作用域链是当前执行上下文的父环境。变量对象通过作用域链查找某个变量的值。this对象是函数调用时的当前对象。
作用域链形成的过程:在函数执行的时候,会首先创建一个新的执行上下文,然后将这个上下文的变量对象加入到作用域链的最顶端,接着将父级执行上下文的作用域链加入到自身作用域链的末尾,最后形成的作用域链便是可以被访问的所有变量、函数或对象的作用域。
作用域链的查找
当JavaScript引擎在执行代码时遇到一个变量,它会先在当前作用域中查找该变量是否被定义,如果没找到,那么就会沿着作用域链向上查找,直到找到为止。如果一直找到全局部还没找到,则会抛出ReferenceError的异常。如果找到了,就直接使用这个变量,而不需要再次创建这个变量。
具体来说,当需要查找某个变量时,JavaScript引擎会先在当前执行上下文中的变量对象中查找该变量,在该对象中找到该变量就不需要再进行后续的查找了;否则就把当前执行上下文的作用域链中的父级执行上下文的变量对象加入到自身作用域链的末尾,然后继续执行上述查找逻辑,直到查找到该变量,或者直到作用域链的末尾。
示例说明
下面通过两个例子进一步演示作用域链的应用:
例子1:全局环境和函数环境之间的作用域链
var a = 1;
function foo() {
var b = 2;
function bar() {
var c = 3;
console.log(a, b, c);
}
bar();
}
foo(); // 输出:1 2 3
在该例中,当执行foo函数时,会首先创建一个名为foo的执行上下文,并将该上下文的变量对象加入到作用域链的顶端。此时作用域链为:foo变量对象 => 全局变量对象
当执行到bar函数时,JavaScript会同样创建一个名为bar的执行上下文,并将该上下文的变量对象加入到作用域链的顶端。此时作用域链为:bar变量对象 => foo变量对象 => 全局变量对象
因此,当bar函数执行时,会先在该函数作用域中查找c
变量,然后在上层的foo作用域中查找b
变量,最后在全局作用域中查找a
变量。
例子2:作用域链的动态性
var a = 10;
function foo() {
console.log(a);
var b = 20;
function bar() {
console.log(a, b, c);
}
bar();
}
foo();
在该例中,当执行foo()
函数时,JavaScript引擎会首先查找当前作用域下的变量对象,由于当前作用域下不存在变量a
,因此JavaScript引擎会沿着作用域链向上查找,找到它的父级作用域,也就是全局作用域,在全局作用域中找到了变量a
的值,因此执行完console.log(a)
语句后,即可输出变量a
的值为10
。
接着JavaScript引擎执行到bar()
函数时,会创建一个新的执行上下文,并将该上下文的变量对象加入到作用域链的顶端。此时作用域链为:bar变量对象 => foo变量对象 => 全局变量对象
由于变量c
未定义,在当前执行上下文中查找不到变量c
,因此JavaScript引擎继续在上一级执行上下文中查找变量c
,但上一级执行上下文依然查找不到变量c
,更上一级执行上下文也查找不到变量c
,最终在全局变量对象中仍然找不到变量c
,因此引擎抛出了ReferenceError错误,提示变量c
未定义。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JS作用域链详解 - Python技术站