JavaScript 作用域链
程序员文章站
2024-02-17 16:36:22
...
概念须知
- 执行环境:执行环境定义了变量或函数是否有权访问去访问其他数据,并决定了它们各自的行为
- 每一个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中。就是说,在JavaScript的后台中,每一个执行环境都有一个变量对象与之对应
- 每一个函数都有自己的执行环境
- 当代码在一个环境中执行时,会创建当前执行环境与之对应的变量对象的一个作用域链
- 作用域链的目的就是保证能够有权有序访问执行环境的所有变量和函数
- 而作用域的前端,始终时当前执行的代码所在环境的变量对象
- 全局执行环境都是作用域链中的最后一个对象
作用域链
现在对作用域链做一个简单的介绍
1. 作用域链其实是由一个个执行环境与之对应的变量对象所组成的
2. 而每一个变量对象存储着当前执行环境声明的所有函数和对象
3. 作用域链的最顶端始终是当前的执行环境对应的变量对象
4. 作用域链的最底部始终是全局执行环境对应的变量对象
5. 而对一个变量的访问始终是从作用域链的最顶端开始,一直沿着作用域链查看每一个执行环境对应的变量对象是否有该变量,有就返回,并停止访问。如果没有就继续向*问作用域链。
6. 如果遍历作用域链中的所有环境变量(就是每一个执行环境与之对应的变量对象)还没有找到该变量,则抛出未定义的异常
例子说明
这里,用一个简单的例子说明变量沿着作用域链的访问过程
// 全局执行环境 将与之对应的环境变量记作global
var a = 1;
var b = 1;
function f1() {
// f1()函数内部的执行环境,将与之对应的环境变量记作f1
var a = 2;
function f2() {
// f2()函数内部的执行环境,将与之对应的环境变量记作f2
// 2
console.log(a + " f2");
// 1
console.log(b + " f2");
// error, c is not defined
// console.log(c);
}
f2();
// 2
console.log(a + " f1");
// 1
console.log(b + " f1");
}
// 1
console.log(a + " global");
f1();
在这段代码中,共涉及到三个执行环境,那么与之对应的就有三个环境变量,它们存储着对应执行环境所声明的变量和函数。
这三个环境变量分别是
- 全局执行环境与之对应的环境变量global
- f1函数执行环境与之对应的环境变量f1
- f1函数执行环境与之对应的环境变量f2
而在这段代码中,这三个环境变量将会构成一个作用域链,如下图所示
有了这幅图,我们就可以非常清楚一个变量的访问过程
一个变量的访问过程可以分为下面几步
- 可以知道当前的作用域链
- 要确定被访问变量所在的执行环境所对应的环境变量
- 将被访问变量所对应的环境变量作为访问作用域链的起点,然后开始向*问
就以f1函数的console.log(a)为例(此处是访问变量a)
1. 首先,确定被访问变量a所在的执行环境对应的环境变量,在这里是f1
2. 然后以该环境变量作为起点向*问作用域链,遇到就返回当前变量所对应的值并停止访问
3. 发生f1环境变量中有变量名为a的变量,返回该变量所对应的值,并停止访问作用域链
因此,console.log(a)所显示的结果是f2函数中声明的变量a,而不是外部全局的变量a
再以f1函数中的console.log(b)为例(此处是访问变量b)
1. 首先,确定被访问变量b所在的执行环境对应的环境变量,在这里是f1
2. 然后以该环境变量作为起点向*问作用域链,遇到就返回当前变量所对应的值并停止访问
3. 在这里,发现环境变量f1中没有变量名为b的变量,于是向*问作用域链,结果访问到全局执行环境对应的环境变量global
4. 发现global环境变量中有变量名为b的变量,于是返回该变量对应的值,停止作用域链的访问
因此,console.log(b)所显示的结果是定义在函数f1外部的变量名为b的变量值
再以f2函数中的console.log(b)为例(此处是访问变量b)
1. 首先,确定被访问变量b所在的执行环境对应的环境变量,在这里是f2
2. 然后以该环境变量作为起点向*问作用域链,遇到就返回当前变量所对应的值并停止访问
3. 在这里,发现环境变量f2中没有变量名为b的变量,于是向*问作用域链,结果访问到f1函数对应的环境变量f1
4. 然后发现f1也没有变量名为b的变量,于是继续沿作用域链向*问,结果访问到全局执行环境对应的环境变量global
5. 发现global环境变量中有变量名为b的变量,于是返回该变量对应的值,停止作用域链的访问
因此,console.log(b)所显示的结果是定义在全局执行环境的变量名为b的变量值
再以f2函数中的console.log(a)为例(此处是访问变量a)
1. 首先,确定被访问变量a所在的执行环境对应的环境变量,在这里是f2
2. 然后以该环境变量作为起点向*问作用域链,遇到就返回当前变量所对应的值并停止访问
3. 在这里,发现环境变量f2中没有变量名为a的变量,于是向*问作用域链,结果访问到f1函数对应的环境变量f1
4. 然后发现f1有变量名ab的变量,于是发f1环境变量中变量名为a的变量值,并停止访问
因此,console.log(a)所显示的结果是定义在f1执行环境的变量名为a的变量值
f2函数中的console.log( c )原理也相同,这里就不作过多阐述了