为什么写这篇:2015 年 6 月,ECMAScript 6.0(俗称 ES6 / ES2015)正式发布。这是 JavaScript 自 1997 年标准化以来最大的一次语法更新——let、const、箭头函数、模板字符串、解构、Promise、Class、模块化、Symbol、Proxy 一次性全部到位。理解 ES2015 之前后的差异,是读懂现代前端框架(Vue/React/Angular)源码的"门槛"。
适用读者:刚开始学 JavaScript 的同学;准备转向 Vue/React/TypeScript 的后端工程师;想系统梳理"老 ES5"和"新 ES6+“区别的中级前端。
前置知识:任意一门编程语言基础(变量、循环、函数)。
目录
- JavaScript 的历史与三件套
- ES2015 引入的新语法全景
- var / let / const 关键差异
- 箭头函数与
this 绑定 - 模板字符串与字符串新方法
- 解构(Destructuring)
- 类(Class)与继承
- Promise:异步编程的第一步
- 模块化(import / export)
- 其他常被忽略但极有用:Symbol / Proxy / for…of
1. JavaScript 的历史与三件套
JavaScript 的故事要从 1995 年说起:
| 年份 | 事件 |
|---|
| 1995 | Netscape(网景)请 Brendan Eich 用 10 天时间设计了一门浏览器脚本语言——Mocha,后改名 LiveScript,最后定名 JavaScript(蹭 Java 热度) |
| 1996 | 微软在 IE 3.0 中推出 JScript,与 JavaScript 形成竞争 |
| 1997 | ECMA(欧洲计算机制造商协会)发布 ECMAScript 1.0 标准(编号 ECMA-262),把 JavaScript 与 JScript 统一到一个规范下 |
| 1999 | ES3 发布,引入 try/catch、switch、do-while |
| 2009 | ES5 发布,引入 strict mode、JSON、Object.create/defineProperty、Array.map/forEach/filter |
| 2015 | ES6 / ES2015 发布——JavaScript 历史上最大的一次更新,从此进入"年年发版"时代 |
| 2016+ | ES2016、ES2017、ES2018… 每年 6 月发一版 |
Why ES2015 重要:它几乎重写了 JavaScript 的语法。Vue、React、Angular、Taro、UniApp——所有现代前端框架都依赖 ES2015+ 的特性来组织代码。
一个完整的 JavaScript 实现由三部分组成(前端老话"三件套”):
| 模块 | 作用 | 关键对象 |
|---|
| ECMAScript | 描述语法、类型、语句、关键字、运算符、对象 | 语言核心 |
| DOM(Document Object Model) | 处理 HTML/XML 文档的 API | document、Element、Node |
| BOM(Browser Object Model) | 与浏览器窗口交互的 API | window、navigator、location、history |
易混点:很多文章把 DOM/BOM 当作 JavaScript 的一部分——严格说,它们是浏览器宿主环境给 JavaScript 提供的 API。同样的 ECMAScript 也能跑在 Node.js 里(没有 DOM,但有 fs/http)。
2. ES2015 引入的新语法全景
ECMAScript 6.0(ES2015)一次性发布了很多新特性。我们按使用频率从高到低梳理:
1
2
3
4
5
6
7
8
9
10
11
12
13
| let、const 和块级作用域 —— 变量声明
箭头函数(Arrow Function) —— 简化函数写法
模板字符串(Template String) —— 字符串拼接 / 多行
对象字面量扩展(Enhanced Object Literals)—— 简写属性 / 方法 / 计算键
解构(Destructuring) —— 数组/对象解构
函数参数扩展(默认 / 剩余 / 展开) —— Default / Rest / Spread
新的数据结构(Set / Map) —— 集合
类(Class) —— 面向对象语法糖
生成器(Generator) —— function* / yield
Promise —— 异步编程
代码模块化(import / export) —— 模块系统
Symbol —— 唯一值
Proxy / Reflect —— 元编程
|
下面的章节挑出最常用的逐个讲。
3. var / let / const 关键差异
这是 ES2015 给 JavaScript 最重要的一课——修掉了 var 的"全局泄漏 + 变量提升"两个大坑。
3.1 三者的对照表
| 特性 | var | let | const |
|---|
| 作用域 | 函数级 | 块级({}) | 块级 |
| 变量提升(hoisting) | 提升,值为 undefined | 提升但有"暂时性死区"(TDZ),使用前必报错 | 同 let |
| 重复声明 | 允许 | 不允许 | 不允许 |
全局声明挂载到 window | 是 | 否 | 否 |
| 重新赋值 | 允许 | 允许 | 不允许(但 const obj = {} 后可改 obj.a) |
| 何时使用 | 老代码 | 变量 | 常量 / 函数表达式 |
3.2 一个 var 造成灾难的经典案例
1
2
3
4
5
6
7
| // 期望:点击不同 li 弹出对应下标
var lis = document.querySelectorAll('li')
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
console.log(i) // 永远是 lis.length
}
}
|
var 是函数作用域,循环结束后 i 变成 lis.length,所有点击都打印同一个值。修法之一是用 let:
1
2
3
4
5
| for (let i = 0; i < lis.length; i++) {
lis[i].onclick = function () {
console.log(i) // 0, 1, 2, ...
}
}
|
Why:let 是块级作用域,每一次循环都创建一份独立的 i 绑定。
3.3 const 不等于"对象不可变"
1
2
3
4
5
6
7
8
| const user = { name: '张三', age: 20 }
// 错误——不能重新赋值
user = { name: '李四' } // TypeError
// 正确——可以改属性
user.age = 21
user.name = '李四'
|
Why:const 锁住的是绑定(变量指向的对象),不是对象本身。数组同理:const arr = [1, 2, 3]; arr.push(4) 是允许的。
3.4 暂时性死区(TDZ)
1
2
| console.log(x) // ReferenceError: Cannot access 'x' before initialization
let x = 10
|
let/const 提升但不初始化,进入 TDZ。使用前必须先声明。这迫使开发者把声明写在使用之前,代码可读性更好。
4. 箭头函数与 this 绑定
箭头函数 = function 的语法糖,但它没有自己的 this——this 永远指向外层词法作用域的 this。
4.1 写法速查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 1. 无参 / 单参
() => 42
x => x * 2
// 2. 多参
(a, b) => a + b
// 3. 多行函数体需要 return
(a, b) => {
const sum = a + b
return sum * 2
}
// 4. 返回对象字面量要加括号
() => ({ name: '张三' })
|
4.2 this 绑定的差异
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // 普通函数:this 取决于调用方式
function Timer() {
this.seconds = 0
setInterval(function () {
this.seconds++ // 错:this 指向 window
console.log(this.seconds)
}, 1000)
}
// 箭头函数:this 继承外层
function Timer2() {
this.seconds = 0
setInterval(() => {
this.seconds++ // 正确:this 指向 Timer2 实例
console.log(this.seconds)
}, 1000)
}
|
不适合箭头函数的场景:
- 对象方法(
obj.foo = () => this 会丢失 this) - 构造函数(不能用
new 调用箭头函数) - 需要动态
this 的事件回调(如 addEventListener)
5. 模板字符串与字符串新方法
5.1 反引号的 4 个能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| const name = '张三'
const age = 20
// 1. 字符串插值
const s1 = `我叫 ${name},今年 ${age}`
// 2. 多行字符串(保留换行)
const sql = `
SELECT * FROM users
WHERE age > ${age}
`
// 3. 表达式
const s2 = `明年我 ${age + 1} 岁`
// 4. 标签模板(高级用法:写 DSL、做 HTML 转义、做 i18n)
function safe(strings, ...values) {
return strings.reduce((acc, str, i) => {
const v = values[i - 1]
return acc + str + (v != null ? String(v).replace(/&/g, '&') : '')
})
}
safe`Hello, ${name}!` // "Hello, 张三!"
|
5.2 ES2015 给字符串加的常用方法
| 方法 | 说明 |
|---|
str.startsWith(prefix) | 是否以 prefix 开头 |
str.endsWith(suffix) | 是否以 suffix 结尾 |
str.includes(sub) | 是否包含 sub |
str.repeat(n) | 重复 n 次 |
str.padStart(len, pad) / padEnd(...) | 补齐长度(常用于对齐输出) |
String.raw\…`` | 不处理转义(用于写 Windows 路径) |
1
| String.raw`C:\Users\Administrator` // 'C:\\Users\\Administrator'
|
6. 解构(Destructuring)
ES2015 的杀手锏之一。核心思想:模式匹配 + 赋值。
6.1 数组解构
1
2
3
4
| const [a, b, c] = [1, 2, 3]
const [first, ...rest] = [1, 2, 3, 4] // first=1, rest=[2,3,4]
const [, second] = [1, 2] // 跳过元素,second=2
const [x = 10, y = 20] = [1] // 默认值,x=1, y=20
|
6.2 对象解构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| const user = { name: '张三', age: 20, address: { city: '北京' } }
// 基本
const { name, age } = user
// 重命名
const { name: userName } = user
// 默认值
const { gender = 'male' } = user
// 嵌套
const { address: { city } } = user
// 函数参数(最常用)
function greet({ name, age }) {
console.log(`Hi, ${name} (${age})`)
}
greet(user)
|
6.3 数组 + 对象混用
1
2
3
4
5
6
7
8
| const data = [
{ name: 'Nick', gender: 1, species: 'Fox' },
{ name: 'Judy', gender: 0, species: 'Bunny' },
]
for (const { name, species } of data) {
console.log(`hi, I am ${name}, and I am a ${species}`)
}
|
7. 类(Class)与继承
ES2015 把 JavaScript 原型继承"语法糖化"了——class 关键字让面向对象写法更接近 Java/C++。
7.1 基本类
1
2
3
4
5
6
7
8
9
10
11
12
| class Animal {
constructor(name) {
this.name = name
}
speak() {
console.log(`${this.name} makes a noise.`)
}
}
const a = new Animal('Cat')
a.speak() // Cat makes a noise.
|
7.2 继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| class Dog extends Animal {
constructor(name, breed) {
super(name) // 必须先调 super
this.breed = breed
}
speak() {
super.speak() // 调父类
console.log(`${this.name} barks.`)
}
}
new Dog('旺财', '柯基').speak()
// 旺财 makes a noise.
// 旺财 barks.
|
注意:class 仍然是基于原型的——class 只是 function + prototype 的语法糖,并不是引入新的 OO 模型。typeof class Foo {} 是 'function'。
7.3 静态方法与私有字段
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Utils {
static id = 0 // 静态属性(ES2022)
#secret = 'hidden' // 私有字段(ES2022)
static nextId() { // 静态方法
return ++Utils.id
}
reveal() { return this.#secret }
}
Utils.nextId() // 1
new Utils().reveal() // 'hidden'
|
8. Promise:异步编程的第一步
ES2015 之前,JavaScript 异步代码全靠回调——“回调地狱"让 fs.readFile 套 5 层变成家常便饭。Promise 把异步操作对象化,提供了 .then / .catch 链式调用。
8.1 三种状态
pending(进行中)fulfilled(已成功)rejected(已失败)
状态一旦变化就冻结,不可逆转。
8.2 基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
| function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) resolve({ id, name: '张三' })
else reject(new Error('invalid id'))
}, 100)
})
}
fetchUser(1)
.then(user => console.log(user.name)) // 张三
.catch(err => console.error(err))
.finally(() => console.log('done'))
|
8.3 Promise 静态方法
1
2
3
4
5
6
7
8
9
10
11
| // 全部成功才成功
Promise.all([p1, p2, p3]).then(([a, b, c]) => {})
// 谁先成功就返回谁
Promise.race([p1, p2]).then(first => {})
// 全部完成(无论成功失败)
Promise.allSettled([p1, p2]).then(results => {})
// 任意一个成功
Promise.any([p1, p2]).then(first => {})
|
进一步:ES2017 的 async/await 是 Promise 的语法糖——让异步代码看起来像同步。await 后面的表达式必须返回 Promise。
1
2
3
4
5
6
7
8
| async function getUser(id) {
try {
const user = await fetchUser(id)
return user.name
} catch (e) {
return 'guest'
}
}
|
9. 模块化(import / export)
ES2015 之前,JavaScript 没有官方模块系统——社区用 CommonJS(Node.js,运行时同步加载)或 AMD(RequireJS,浏览器异步加载)。ES2015 一锤定音:静态 import / export。
9.1 命名导出 / 默认导出
1
2
3
4
| // utils.js
export function add(a, b) { return a + b }
export const PI = 3.14159
export default class Calculator { /* ... */ }
|
1
2
3
| // main.js
import Calculator, { add, PI } from './utils.js'
import * as utils from './utils.js' // 全部命名导出作为对象
|
Why “静态”:import 必须在文件顶部、不能用条件判断、会被构建工具静态分析 → tree shaking、循环依赖检测、按需打包。
9.2 与 CommonJS 互操作
1
2
3
4
5
6
7
| // CommonJS 侧
module.exports = { add, PI }
exports.sub = (a, b) => a - b
// ESM 侧消费
import pkg from './utils.cjs' // 默认导入 = module.exports
import { sub } from './utils.cjs' // 命名导入 = exports.sub
|
10. 其他常被忽略但极有用:Symbol / Proxy / for…of
10.1 Symbol:唯一标识符
1
2
3
4
5
6
7
8
9
| const RED = Symbol('red')
const RED2 = Symbol('red')
RED === RED2 // false
// 常用于对象"私有键"(避免被意外覆盖)
const user = {
[Symbol('id')]: 1001,
name: '张三'
}
|
10.2 Proxy:拦截对象操作
1
2
3
4
5
6
7
8
9
10
11
12
13
| const obj = new Proxy({}, {
get(target, key) {
console.log(`get ${key}`)
return target[key]
},
set(target, key, value) {
console.log(`set ${key} = ${value}`)
target[key] = value
}
})
obj.foo = 1 // set foo = 1
obj.foo // get foo → 1
|
应用:Vue 3 的响应式系统就是用 Proxy 实现的——拦截 get/set 自动收集依赖、触发更新。
10.3 for…of:统一迭代器接口
1
2
3
4
5
6
7
8
9
10
| // 数组
for (const item of [1, 2, 3]) console.log(item)
// 字符串(按 Unicode 码点)
for (const ch of '你好') console.log(ch) // '你' '好'
// Set / Map
for (const [k, v] of new Map([['a', 1]])) console.log(k, v)
// 任何实现了 [Symbol.iterator] 的对象
|
vs for…in:for...in 遍历键名(对象所有可枚举属性,包括原型链);for...of 遍历值(必须实现迭代器协议)。
小结:一份"现代 JavaScript 入门"清单
如果你刚学 JavaScript,按下面顺序练习最高效:
- 学会
let/const、箭头函数、模板字符串 → 写变量与函数 - 学会对象/数组解构 → 简化代码
- 学会
class 与模块化 → 写小项目 - 学会
Promise 与 async/await → 处理异步 - 学会
Proxy / Symbol / 迭代器协议 → 读懂框架源码
下一步:本文只覆盖 ES2015 一个版本。2016 年起的 Array.includes、async/await、Object.values、Object.entries、?. 可选链、?? 空值合并、Array.at(-1) 等现代语法都值得系统学。Vue 3 / React 18+ 的源码里几乎只用 ES2015+。
参考资料