Featured image of post JavaScript 核心与 ES2015+ 新特性速览

JavaScript 核心与 ES2015+ 新特性速览

从 JavaScript 的历史起源与核心三件套(ECMAScript/DOM/BOM),到 ES2015(ES6)引入的 let/const/箭头函数/解构/Promise/Class 等现代语法,再到 var/let/const 的关键差异。一篇打通 JavaScript 入门与现代语法的全景。

为什么写这篇:2015 年 6 月,ECMAScript 6.0(俗称 ES6 / ES2015)正式发布。这是 JavaScript 自 1997 年标准化以来最大的一次语法更新——letconst、箭头函数、模板字符串、解构、Promise、Class、模块化、Symbol、Proxy 一次性全部到位。理解 ES2015 之前后的差异,是读懂现代前端框架(Vue/React/Angular)源码的"门槛"。

适用读者:刚开始学 JavaScript 的同学;准备转向 Vue/React/TypeScript 的后端工程师;想系统梳理"老 ES5"和"新 ES6+“区别的中级前端。

前置知识:任意一门编程语言基础(变量、循环、函数)。

目录

  1. JavaScript 的历史与三件套
  2. ES2015 引入的新语法全景
  3. var / let / const 关键差异
  4. 箭头函数与 this 绑定
  5. 模板字符串与字符串新方法
  6. 解构(Destructuring)
  7. 类(Class)与继承
  8. Promise:异步编程的第一步
  9. 模块化(import / export)
  10. 其他常被忽略但极有用:Symbol / Proxy / for…of

1. JavaScript 的历史与三件套

JavaScript 的故事要从 1995 年说起:

年份事件
1995Netscape(网景)请 Brendan Eich 用 10 天时间设计了一门浏览器脚本语言——Mocha,后改名 LiveScript,最后定名 JavaScript(蹭 Java 热度)
1996微软在 IE 3.0 中推出 JScript,与 JavaScript 形成竞争
1997ECMA(欧洲计算机制造商协会)发布 ECMAScript 1.0 标准(编号 ECMA-262),把 JavaScript 与 JScript 统一到一个规范下
1999ES3 发布,引入 try/catchswitchdo-while
2009ES5 发布,引入 strict mode、JSON、Object.create/definePropertyArray.map/forEach/filter
2015ES6 / ES2015 发布——JavaScript 历史上最大的一次更新,从此进入"年年发版"时代
2016+ES2016、ES2017、ES2018… 每年 6 月发一版

Why ES2015 重要:它几乎重写了 JavaScript 的语法。Vue、React、Angular、Taro、UniApp——所有现代前端框架都依赖 ES2015+ 的特性来组织代码。

一个完整的 JavaScript 实现由三部分组成(前端老话"三件套”):

模块作用关键对象
ECMAScript描述语法、类型、语句、关键字、运算符、对象语言核心
DOM(Document Object Model)处理 HTML/XML 文档的 APIdocumentElementNode
BOM(Browser Object Model)与浏览器窗口交互的 APIwindownavigatorlocationhistory

易混点:很多文章把 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 三者的对照表

特性varletconst
作用域函数级块级({}块级
变量提升(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, ...
  }
}

Whylet 是块级作用域,每一次循环都创建一份独立的 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 = '李四'

Whyconst 锁住的是绑定(变量指向的对象),不是对象本身。数组同理: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, '&amp;') : '')
  })
}
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…infor...in 遍历键名(对象所有可枚举属性,包括原型链);for...of 遍历(必须实现迭代器协议)。


小结:一份"现代 JavaScript 入门"清单

如果你刚学 JavaScript,按下面顺序练习最高效:

  1. 学会 let/const、箭头函数、模板字符串 → 写变量与函数
  2. 学会对象/数组解构 → 简化代码
  3. 学会 class 与模块化 → 写小项目
  4. 学会 Promiseasync/await → 处理异步
  5. 学会 Proxy / Symbol / 迭代器协议 → 读懂框架源码

下一步:本文只覆盖 ES2015 一个版本。2016 年起的 Array.includesasync/awaitObject.valuesObject.entries?. 可选链、?? 空值合并、Array.at(-1) 等现代语法都值得系统学。Vue 3 / React 18+ 的源码里几乎只用 ES2015+。

参考资料

使用 Hugo 构建
主题 StackJimmy 设计