一文彻底搞懂 JavaScript 作用域

509 阅读4分钟

在学习 JavaScript 的过程中,作用域(Scope)是一个非常核心但又容易被初学者忽视的概念。理解作用域不仅能帮助你写出更健壮的代码,还能让你在调试和优化时事半功倍。本文将带你系统梳理 JS 作用域的相关知识,助你轻松迈过这道门槛。

一、什么是作用域?

作用域,简单来说,就是变量的“可见范围”。在某个作用域内声明的变量,只能在该作用域及其子作用域中访问和修改。作用域的存在,保证了变量不会被随意访问和修改,提升了代码的安全性和可维护性。

二、JS 引擎 V8 与作用域

JavaScript 代码的执行离不开 JS 引擎,最著名的就是 Chrome 和 Node.js 所采用的 V8 引擎。V8 执行 JS 代码的过程大致分为以下几步:

  1. 分词/词法分析:将代码拆分成有意义的词法单元(Token)。
  2. 解析/语法分析:将 Token 解析成抽象语法树(AST)。
  3. 代码生成:将 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 引入了 letconst,使得 JavaScript 拥有了块级作用域。用 letconst{} 内声明的变量,只在该块内有效。

{
  let c = 30;
  const d = 40;
  console.log(c, d); // 30 40
}
console.log(c, d); // 报错,c 和 d 未定义

四、作用域的查找规则

JavaScript 采用“词法作用域”,也叫“静态作用域”。变量的作用域在代码编写阶段就已经确定,而不是在运行时动态决定。

查找规则如下:

  1. 先在当前作用域查找变量;
  2. 如果找不到,就去上一级作用域查找;
  3. 一直查找到全局作用域为止;
  4. 只能从内到外查找,不能反向查找。

举个例子:

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 的区别

  1. letconst 声明的变量不存在“声明提升”,而 var 声明的变量会被提升到作用域顶部。
  2. letconst 不能重复声明同名变量,var 可以。
  3. var 声明的全局变量会被挂载到 window 对象上,letconst 不会。
  4. 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 的基础知识,理解它有助于你写出更安全、可维护的代码。牢记作用域的分类、查找规则以及 letconstvar 的区别,能让你在开发中游刃有余。