2015 年 6 月,ECMAScript 6(简称 ES6,官方名称 ES2015)正式发布,这是 JavaScript 语言诞生以来规模最大的一次更新,甚至可以说,它把 JavaScript 从"脚本语言"推向了"工程语言"。在此之前,var 的函数作用域陷阱、层层嵌套的回调、缺乏模块系统等问题,让大型前端项目举步维艰。ES6 一口气带来了块级作用域、箭头函数、解构赋值、模板字符串和原生模块等特性,如今它们已是所有现代框架和 Node.js 项目的共同语法基础。顺便说一句,ES6 与 ES2015 是同一个东西:前者是流传更广的宣传名,后者是按年份编号的官方名,本文混用不区分。

1. let 与 const:告别 var 的坑

在 ES6 之前,声明变量只有 var 一种方式,而它有两个著名的问题:一是函数作用域而非块级作用域,循环里声明的变量在循环外依然可见;二是变量提升,导致"先使用、后声明"也能运行,容易埋下难以察觉的隐患。let 与 const 把作用域收窄到所在的花括号内,并引入"暂时性死区"(TDZ):在声明语句之前访问变量会直接抛出 ReferenceError,把错误提前暴露出来。const 更进一步,声明时必须赋值且之后不能再重新赋值,适合表达"这个值不会变"的意图。注意,const 冻结的只是变量与值的"绑定",如果值是对象,对象的属性仍然可以修改。

// var:函数作用域 + 变量提升
function demo() {
  console.log(a); // undefined,不会报错
  var a = 1;
}

// let:块级作用域
{
  let x = 1;
  console.log(x); // 1
}
// console.log(x); // ReferenceError:x 不在作用域内

// const:声明时必须赋值,且不能重新赋值
const PI = 3.14;
// PI = 3; // TypeError:Assignment to constant variable

// 但 const 对象的内容可以修改
const user = { name: "小明" };
user.name = "小红"; // 合法
声明方式作用域变量提升可重复声明可重新赋值
var函数是(得到 undefined)
let否(TDZ 报错)
const否(TDZ 报错)

2. 箭头函数:更简洁,this 更省心

箭头函数用 => 取代 function 关键字,写法紧凑;更关键的是它没有自己的 this,会继承定义时所在作用域的 this。以前回调函数里 this 丢失,得用 that = this 或 bind 兜底,箭头函数让这类问题基本消失。代价有两个:箭头函数不能用作构造函数(用 new 调用会抛错),也没有自己的 arguments 对象(可以用 rest 参数替代)。判断规则很简单:需要动态 this 的场景(对象方法、构造函数)用普通函数,其余场景放心用箭头函数。另外,箭头函数也没有 prototype 属性,这进一步印证了它不适合作为构造函数。

// 基本写法:多参数 + 单条表达式
const add = (a, b) => a + b;

// 单个参数可省略括号,单条语句可省略 return
const double = n => n * 2;

// 返回对象字面量必须加括号
const createUser = name => ({ name });

// this 的差异:箭头函数继承外层 this
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++; // 这里的 this 指向 Timer 实例
  }, 1000);
}

3. 解构赋值:按模式批量取值

解构可以理解为"按照模式批量取值"。数组解构按位置取,对象解构按属性名取,两者都支持默认值,还能用 ...rest 收集剩余项。最常见的两个用法:一是从接口返回的对象中一次性取出需要的字段,代码立刻短一大截;二是函数参数直接解构,调用时不用再纠结参数顺序。配合默认参数,可选字段的语义一目了然。嵌套对象也可以逐层解构,不过嵌套太深时建议拆开写,保持可读性。解构还有一个经典用法:交换两个变量的值,[a, b] = [b, a] 一行搞定,比引入临时变量直观。

// 数组解构:按位置取值
const [first, second, ...rest] = [10, 20, 30, 40];
// first = 10,second = 20,rest = [30, 40]

// 对象解构:按属性名取值,可重命名、设默认值
const user = { id: 1, name: "小明" };
const { id, name: userName, age = 18 } = user;
// id = 1,userName = "小明",age = 18

// 函数参数解构
function render({ title, width = 100 }) {
  console.log(title, width);
}
render({ title: "Hello" }); // Hello 100

4. 模板字符串与默认参数

模板字符串用反引号包裹,支持换行和 ${} 插值,拼 HTML、拼 SQL、拼路径时比字符串拼接干净得多,也彻底告别了转义引号的痛苦。默认参数让"可选参数"有了标准写法:传入 undefined 时自动使用默认值,而且默认值可以引用前面的参数。

// 模板字符串:支持换行与插值
const name = "CodeLab";
const html = `
  <h1>欢迎来到 ${name}</h1>
  <p>今天共有 ${3 + 4} 篇文章</p>
`;

// 默认参数
function greet(name, prefix = "你好") {
  return `${prefix},${name}`;
}
greet("小明");       // 你好,小明
greet("小明", "Hi"); // Hi,小明

5. 模块系统:import 与 export

ES6 模块(ESM)是 JavaScript 官方第一次提供模块能力。它用 export 导出、import 导入,支持命名导出与默认导出。与 Node.js 早期使用的 CommonJS 相比,ESM 是静态的:import 语句出现在顶层、模块名是字面量,编译器可以在运行前分析依赖关系,从而做 tree shaking(摇树优化),打包时剔除未使用的代码。使用时要留意加载方式:浏览器通过 <script type="module"> 加载,Node.js 里把文件后缀改成 .mjs,或在 package.json 中声明 "type": "module"。如果只是个人练习,CommonJS 在 Node 里依然完全可用,但新项目建议直接上 ESM——它也是 Deno、Bun 等新一代运行时唯一推荐的模块方式。

// math.js:命名导出 + 默认导出
export const PI = 3.14159;
export function square(x) {
  return x * x;
}
export default class Calculator {}
// main.js:混合导入
import Calculator, { PI, square } from "./math.js";
console.log(square(PI)); // 9.8696...

6. 如何在你的环境里启用

ES6 的语法特性(let/const、箭头函数、解构、模板字符串)在 2017 年之后发布的所有主流浏览器中都原生支持;Node.js 从 6.x 开始逐步支持,12.x 起完整支持,现在的 LTS 版本(18/20/22)更是不在话下。真正需要注意的只有 ES 模块:浏览器需要 <script type="module">,Node.js 需要 "type": "module" 或 .mjs 后缀。如果项目还要兼容 IE 等上古浏览器,可以引入 Babel 把 ES6 转译成 ES5,但到 2026 年的今天,这种需求已经很少见了。如果拿不准某个特性在目标环境是否可用,可以查 caniuse.com,或直接在 Node 里跑一段验证代码;语法级的问题用 node --check 就能提前发现。

特性浏览器支持起点Node.js 支持起点
let / constChrome 49+4+
箭头函数Chrome 45+4+
解构赋值Chrome 49+6+
模板字符串Chrome 41+4+
ES 模块Chrome 61+12+(完整)

7. 值不值得用

这个问题没有悬念:不是"值不值得",而是"必须用"。ES6 是现代 JavaScript 的最小公约数,几乎所有框架、工具链和面试题都建立在它之上。具体建议:把 var 全面替换为 let/const,新代码习惯性使用箭头函数、解构与模板字符串;模块系统则应该成为所有新项目组织代码的默认方式。唯一要克制的是,不要为了炫技把简单代码写成复杂的解构嵌套——可读性永远是第一位的。

💡 学习建议:不要只看不练。打开浏览器控制台或 Node.js REPL,把文中的例子亲手敲一遍;再翻出一段自己写的旧代码,用 let/const、箭头函数、解构、模板字符串重写一遍,对比可读性的变化,比读十遍教程都有效。