在学习 JavaScript 的过程中,作用域(Scope)是一个非常核心但又容易被初学者忽视的概念。理解作用域不仅能帮助你写出更健壮的代码,还能让你在调试和优化时事半功倍。本文将带你系统梳理 JS 作用域的相关知识,助你轻松迈过这道门槛。
一、什么是作用域?
作用域,简单来说,就是变量的“可见范围”。在某个作用域内声明的变量,只能在该作用域及其子作用域中访问和修改。作用域的存在,保证了变量不会被随意访问和修改,提升了代码的安全性和可维护性。
二、JS 引擎 V8 与作用域
JavaScript 代码的执行离不开 JS 引擎,最著名的就是 Chrome 和 Node.js 所采用的 V8 引擎。V8 执行 JS 代码的过程大致分为以下几步:
- 分词/词法分析:将代码拆分成有意义的词法单元(Token)。
- 解析/语法分析:将 Token 解析成抽象语法树(AST)。
- 代码生成:将 AST 转换为字节码,供后续执行。
在这个过程中,作用域的划分和变量的查找规则也被确定下来。
三、作用域的分类
JavaScript 中主要有三种作用域:
1. 全局作用域
在任何函数体外声明的变量,属于全局作用域。全局变量可以在任意地方访问和修改。
var a = 10;
function foo() {
console.log(a); // 10
}
foo();
2. 函数作用域
在函数内部用 var 声明的变量,只能在该函数内部访问,外部无法访问。
function bar() {
var b = 20;
console.log(b); // 20
}
bar();
console.log(b); // 报错,b 未定义
3. 块级作用域
ES6 引入了 let 和 const,使得 JavaScript 拥有了块级作用域。用 let 或 const 在 {} 内声明的变量,只在该块内有效。
{
let c = 30;
const d = 40;
console.log(c, d); // 30 40
}
console.log(c, d); // 报错,c 和 d 未定义
四、作用域的查找规则
JavaScript 采用“词法作用域”,也叫“静态作用域”。变量的作用域在代码编写阶段就已经确定,而不是在运行时动态决定。
查找规则如下:
- 先在当前作用域查找变量;
- 如果找不到,就去上一级作用域查找;
- 一直查找到全局作用域为止;
- 只能从内到外查找,不能反向查找。
举个例子:
var x = 1;
function outer() {
var x = 2;
function inner() {
console.log(x);
}
inner();
}
outer(); // 输出 2
inner 函数会先在自己作用域查找 x,找不到就去 outer 作用域查找,最终输出 2。
五、作用域的“欺骗”——with 和 eval
JavaScript 中有两个特殊语法可以“欺骗”词法作用域:
1. with
with 语句可以将一个对象的属性临时添加到作用域链的前端。这样在 with 代码块中访问变量时,会优先查找该对象的属性。
var obj = { a: 1 };
with (obj) {
a = 2;
b = 3;
}
console.log(obj.a); // 2
console.log(window.b); // 3(b 被添加到全局作用域)
注意: 当 with 修改对象不存在的属性时,会在全局作用域中创建新属性,容易引发 bug,因此不推荐使用。
2. eval
eval 可以将字符串当作代码执行,并将其中的变量强行添加到当前作用域。
var e = 5;
eval('var e = 6;');
console.log(e); // 6
注意: 滥用 eval 会导致作用域混乱,降低代码安全性和性能,实际开发中应尽量避免。
六、let、const、var 的区别
let和const声明的变量不存在“声明提升”,而var声明的变量会被提升到作用域顶部。let和const不能重复声明同名变量,var可以。var声明的全局变量会被挂载到window对象上,let和const不会。const声明时必须赋值,且赋值后不能修改。
示例:
console.log(a); // undefined
var a = 1;
console.log(b); // 报错
let b = 2;
七、块级作用域的应用
块级作用域常用于 for 循环、if 语句等代码块中,防止变量“泄漏”到外部作用域。
for (let i = 0; i < 3; i++) {
// i 只在 for 循环内部有效
}
console.log(i); // 报错,i 未定义
八、总结
作用域是 JavaScript 的基础知识,理解它有助于你写出更安全、可维护的代码。牢记作用域的分类、查找规则以及 let、const、var 的区别,能让你在开发中游刃有余。