网站
引入
在 JS 作用域 里学过,变量有各自的有效范围。
那问题来了:如果当前作用域里找不到一个变量,JS 会怎么办?
答案是:顺着作用域链,一层一层往外找。
正文
定义
定义
作用域链是 JS 查找变量时遵循的路径。从当前作用域开始,逐级向外层查找,直到全局作用域为止。
变量查找规则
当访问一个变量时,JS 的查找顺序:
- 先在当前作用域找
- 找不到就去外层作用域找
- 还找不到就继续往外,一直到全局作用域
- 全局作用域也找不到,就报
ReferenceError
let color = "红色";
function outer() {
let color = "蓝色";
function inner() {
console.log(color);
}
inner();
}
outer();输出 "蓝色"。
inner() 自己没有 color,去外层 outer() 里找到了 "蓝色",就不再往外找了。
找到就停,不会继续往外找。
作用域嵌套
函数可以嵌套,作用域也跟着嵌套。
let a = 1;
function level1() {
let b = 2;
function level2() {
let c = 3;
function level3() {
let d = 4;
console.log(a, b, c, d);
}
level3();
}
level2();
}
level1();输出:1 2 3 4
level3() 的作用域链:
level3 自身 → level2 → level1 → 全局
所以它可以访问到所有层级的变量。
作用域链的图
用一个简单的图来表示查找方向:
全局作用域 ← 最后查找
└── level1 作用域 ← 第三层
└── level2 作用域 ← 第二层
└── level3 作用域 ← 最先查找
查找方向:从内往外,从下往上。
变量遮蔽
如果内层和外层有同名变量,内层的会遮蔽外层的。
let name = "全局";
function test() {
let name = "函数";
console.log(name);
}
test();输出 "函数"。
当前作用域有 name,就用自己的,不会去外面找。
这和找东西一样:手边有的就不会去翻柜子。
找不到变量的情况
function test() {
console.log(x);
}
test();报错:ReferenceError: x is not defined
JS 从 test() 内部找到全局,都没找到 x,就报错了。
多层函数实战
看看下面这段代码的输出:
let fruit = "苹果";
function kitchen() {
let fruit = "香蕉";
function fridge() {
let fruit = "橘子";
function eat() {
console.log("吃了" + fruit);
}
eat();
}
fridge();
}
kitchen();输出 "吃了橘子"。
eat() 自己有 fruit,直接用自己的 "橘子"。
如果去掉 eat() 里的 fruit:
let fruit = "苹果";
function kitchen() {
let fruit = "香蕉";
function fridge() {
let fruit = "橘子";
function eat() {
console.log("吃了" + fruit);
}
eat();
}
fridge();
}
kitchen();还是 "吃了橘子",因为 fridge() 里有 fruit。
再去掉 fridge() 里的 fruit:
let fruit = "苹果";
function kitchen() {
let fruit = "香蕉";
function fridge() {
function eat() {
console.log("吃了" + fruit);
}
eat();
}
fridge();
}
kitchen();输出 "吃了香蕉",找到了 kitchen() 里的 fruit。
作用域链在函数定义时确定
作用域链是在函数定义时就确定好的,不是调用时。
let color = "红色";
function showColor() {
console.log(color);
}
function changeAndCall() {
let color = "蓝色";
showColor();
}
changeAndCall();输出 "红色"。
虽然 showColor() 是在 changeAndCall() 里面被调用的,但它的作用域链是在定义时就固定了。
showColor 定义在全局作用域,所以它只认全局的 color。
常见错误
误以为调用位置影响作用域链
let x = 10;
function a() {
console.log(x);
}
function b() {
let x = 20;
a();
}
b();输出 10,不是 20。
a() 的作用域链是 a → 全局,跟 b 没有关系。
忘记变量被遮蔽
let count = 100;
function test() {
let count = 0;
count = count + 1;
console.log(count);
}
test();
console.log(count);输出:
1
100函数内部的 count 遮蔽了全局的 count,修改的也是内部的。
特点
- 变量查找从当前作用域开始,逐层向外
- 找到就停止,找不到就报
ReferenceError - 内层变量会遮蔽外层同名变量
- 作用域链在函数定义时确定,和调用位置无关
- 嵌套越深,作用域链越长
理解
把作用域链理解成找东西时的习惯。
你在自己书桌上找不到笔,就去客厅找。客厅没有,就去小区超市找。
找到一支就不找了,不会跑更远的地方。
如果到处都找不到,就说”找不到”(报错)。
作用域链就是这个查找路线:从最近的开始,一层一层往外翻,找到就停。
引出
理解了作用域链之后,可以继续学习 JS 闭包,看函数是怎么在作用域链上”记住”外层变量的。
也可以学习 JS 执行机制,了解代码在浏览器里到底是怎么一步步运行的。