ES6 完全使用手册(更新版)
Category(分类): JavaScript Status: 已更新
原文作者:冴羽 历史文章:ES6 完全使用手册
原文中的“ES6”主要指 ES2015 的新语法,同时混入了 ES2016、ES2018、ES2020 以及当时尚未定案的提案。本文尽量保留原来的章节和使用场景,并把每项特性标记为正式标准、较新的标准或仍在提案阶段。代码示例也修复了抓取造成的注释粘连、拼写错误、变量未声明和运行时语义错误。
前言:先分清版本和提案
严格来说:
- ECMAScript 2015(ES2015,常被称为 ES6):
let/const、模板字符串、箭头函数、类、模块、解构、迭代器/生成器、Promise、Map/Set、Symbol等; - ECMAScript 2016(ES7):
Array.prototype.includes()、指数运算符; - ECMAScript 2017(ES8):
async/await、Object.values()、Object.entries()、字符串填充、尾逗号、Object.getOwnPropertyDescriptors(); - 对象展开是 ES2018;可选链是 ES2020;逻辑赋值是 ES2021;
- 装饰器和管道操作符不是“ES6 语法”。它们的标准化进度和工具链支持需要单独说明,不能看到 Babel 插件就认为浏览器原生支持。
ECMAScript 每年发布一个版本。实际项目应以目标运行时、构建工具和兼容性需求为准,而不是简单地把所有新语法都称为 ES6。
1. let 和 const
let 和 const 都是块级作用域声明,var 是函数作用域声明。现代代码通常默认使用 const,只有变量本身需要重新赋值时才使用 let:
// 历史写法
var foo = 'bar'
// 需要重新赋值时使用 let
let count = 0
count += 1
// 引用不需要重新赋值时使用 const
const name = 'Alice'
const 只保护变量绑定,不会冻结对象或数组:
const user = { name: 'Alice' }
user.name = 'Bob' // 可以修改对象内部属性
// user = {} // TypeError,不能让 user 指向新对象
如果需要禁止对象扩展、修改或删除属性,应根据需求使用 Object.preventExtensions()、Object.seal() 或 Object.freeze();它们也只提供浅层保护,深层对象仍需单独处理。
暂时性死区
let/const 声明会被提升到块作用域,但在声明执行前处于暂时性死区(TDZ):
console.log(typeof value) // ReferenceError,而不是 'undefined'
let value = 1
这也是 let/const 与 var 的重要区别。不要依赖变量提升来组织复杂初始化逻辑。
2. 模板字符串
模板字符串使用反引号,可以插入表达式,也可以跨行:
const first = 'Alice'
const last = 'Wang'
const message = `Your name is ${first} ${last}.`
${...} 中可以放任意表达式,但不会自动转义 HTML:
const userInput = '<img src=x onerror=alert(1)>'
const html = `<p>${userInput}</p>` // 直接插入 DOM 时可能产生 XSS
向页面插入用户输入时,应使用 textContent 或经过可靠的 HTML 清洗,而不是把模板字符串当作安全模板。
标签模板
标签模板会把字符串片段和插值作为参数传给函数:
function inspect(strings, ...values) {
return {
strings: [...strings],
values
}
}
const name = 'Alice'
console.log(inspect`Hello ${name}!`)
// { strings: ['Hello ', '!'], values: ['Alice'] }
原文提到的 oneLine 是一个自定义标签函数,不是 JavaScript 内置 API。标签函数的实现必须自己处理空白、转义和插值;不能因为使用了反引号就认为字符串已被 HTML、SQL 或 URL 安全转义。
3. 箭头函数
箭头函数的基本形式如下:
const add = (a, b) => a + b
const square = value => value * value
const log = value => {
console.log(value)
}
箭头函数没有自己的 this、arguments、prototype,也不能用 new 调用。它适合回调和需要捕获外层 this 的场景,但不是普通函数的完全替代品。
不要用箭头函数定义需要动态 this 的对象方法
const object = {
value: 1,
getValue() {
return this.value
},
wrong: () => this.value
}
console.log(object.getValue()) // 1
// object.wrong() 的 this 来自定义箭头函数的外层环境,不是 object
对象字面量本身不会形成新的 this 词法环境。需要动态 this 时使用方法简写或普通函数。
原型方法也不要写成共享箭头属性
function Foo(value) {
this.value = value
}
Foo.prototype.getValue = function () {
return this.value
}
如果写成 Foo.prototype.getValue = () => this.value,调用时不会把 Foo 实例绑定给箭头函数。另一方面,现代类字段中的箭头函数会为每个实例创建一份函数,适合自动绑定,但不等同于原型方法。
事件回调要看你需要哪个 this
button.addEventListener('click', function () {
// 浏览器事件监听器中的 this 是 button
this.textContent = 'Clicked'
})
class View {
handleClick = () => {
// 类字段箭头函数捕获实例 this
this.update()
}
update() {}
}
原文中的 React componentWillMount 已废弃;React 或其他框架的生命周期应使用当前版本文档推荐的 API。
4. Symbol
1. 唯一的属性键
每次调用 Symbol() 都会创建一个不同的符号:
const first = Symbol('id')
const second = Symbol('id')
console.log(first === second) // false
const user = {
[first]: 100
}
符号键不会出现在 for...in、Object.keys() 或 JSON 序列化中,但它们不是绝对私有的:
console.log(Object.getOwnPropertySymbols(user)) // [first]
console.log(Reflect.ownKeys(user)) // [first]
Symbol.for() 使用全局符号注册表,语义与 Symbol() 不同:
const a = Symbol.for('shared')
const b = Symbol.for('shared')
console.log(a === b) // true
console.log(Symbol.keyFor(a)) // shared
2. 避免属性名冲突
历史代码可能用很长的字符串作为“私有”属性名:
const isMoving = Symbol('isMoving')
if (element[isMoving]) {
smoothAnimations(element)
}
element[isMoving] = true
这可以降低普通字符串属性名冲突的概率,但不能阻止拥有对象引用的代码通过反射找到符号。真正需要封装时,应考虑闭包、模块私有绑定或 #privateField。
3. 消除魔术字符串
原文把资源类型从字符串改成 Symbol,这种做法可避免不同库使用相同字符串时的冲突:
const TYPE_AUDIO = Symbol('audio')
const TYPE_VIDEO = Symbol('video')
const TYPE_IMAGE = Symbol('image')
function handleFileResource(resource) {
switch (resource.type) {
case TYPE_AUDIO:
return playAudio(resource)
case TYPE_VIDEO:
return playVideo(resource)
case TYPE_IMAGE:
return previewImage(resource)
default:
throw new Error('Unknown type of resource')
}
}
如果值需要持久化、显示给用户或与后端交换,字符串或枚举对象往往更合适;不要为了“避免魔术字符串”无条件使用符号。
5. Set 和 Map
1. 数组去重
const array = [1, 2, 2, 3, NaN, NaN]
const unique = [...new Set(array)]
console.log(unique) // [1, 2, 3, NaN]
Set 使用 SameValueZero 判断相等,因此可以把 NaN 视为相等;它不负责深度去重,两个结构相同但引用不同的对象仍是两个元素。
2. Map 与对象映射
原文使用对象或 Map 根据颜色查找水果:
const fruitByColor = {
red: ['apple', 'strawberry'],
yellow: ['banana', 'pineapple'],
purple: ['grape', 'plum']
}
function fruitsByObject(color) {
return Object.hasOwn(fruitByColor, color)
? fruitByColor[color]
: []
}
const fruitByColorMap = new Map([
['red', ['apple', 'strawberry']],
['yellow', ['banana', 'pineapple']],
['purple', ['grape', 'plum']]
])
function fruitsByMap(color) {
return fruitByColorMap.get(color) ?? []
}
对象的键主要是字符串和 Symbol,并且会受到原型、枚举规则和特殊键名影响;Map 可以使用任意值作为键,并明确提供 has、get、set、size 等操作。小型固定配置用对象通常更简单,不要为了追求“更现代”而盲目替换。
6. for...of
for...of 遍历的是可迭代对象的值,而不是对象的所有属性。常见可迭代对象包括:
- 数组、字符串;
Set、Map;- 生成器对象;
- TypedArray;
- 支持迭代器协议的现代 DOM 集合,例如多数浏览器中的
NodeList; - 自定义实现了
[Symbol.iterator]()的对象。
for (const value of ['a', 'b', 'c']) {
console.log(value)
}
for (const [index, value] of ['a', 'b', 'c'].entries()) {
console.log(index, value)
}
它的一个优势是可以使用 break、continue,并且在退出时有机会触发迭代器的 return() 清理逻辑:
for (const value of [1, 2, 3, 4]) {
if (value === 3) break
console.log(value)
}
遍历 Map
const map = new Map([
['name', 'Alice'],
['age', 18]
])
for (const key of map.keys()) console.log(key)
for (const value of map.values()) console.log(value)
for (const [key, value] of map) console.log(key, value)
for...of 不能直接遍历普通对象:
const object = { a: 1, b: 2 }
for (const [key, value] of Object.entries(object)) {
console.log(key, value)
}
7. Promise
Promise 是 ECMAScript 标准内置的异步结果抽象。它解决的是回调结果的组合和错误传播,不会让耗时的 CPU 计算自动移到后台线程,也没有通用的内置取消方法。
1. 基本链式调用
原文用嵌套回调表达连续异步操作;现代代码可以写成 Promise 链:
request(url, (error, body) => {
if (error) return handleError(error)
writeFileAsync('1.txt', body, error => {
if (error) return handleError(error)
request(url2, (error, result) => {
if (error) return handleError(error)
console.log(result)
})
})
})
如果 API 返回 Promise:
requestAsync(url)
.then(body => writeFileAsync('1.txt', body))
.then(() => requestAsync(url2))
.then(console.log)
.catch(handleError)
then() 总是返回新的 Promise;回调中返回的值会成为下一个 then 的输入,返回 Promise 则会等待它结算。不要启动异步操作却不返回它,否则会产生 floating promise:
requestAsync(url)
.then(body => writeFileAsync('1.txt', body))
.then(() => console.log('文件已经写完'))
2. finally
Promise.prototype.finally() 在 ES2018 标准化,用于无论成功还是失败都执行清理:
fetch('/data.json')
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
})
.catch(error => console.error(error))
.finally(() => console.log('finished'))
finally 通常不会改变原来的结果;但如果 finally 回调抛错或返回拒绝的 Promise,链会变成拒绝。
8. async/await
async 函数总是返回 Promise。await 会暂停当前异步函数,等待值被兑现或拒绝;它不会阻塞整个 JavaScript 执行环境。即使等待的是普通值,异步函数也会在后续异步时机继续执行。
1. 让顺序逻辑更清晰
async function loadData() {
await fetchData()
return 'done'
}
等价的 Promise 写法是:
function loadDataWithPromise() {
return fetchData().then(() => 'done')
}
多个有依赖关系的步骤可以这样写:
async function loadMoreData() {
const data = await fetchData()
if (data.moreData) {
return fetchAnotherData(data)
}
return data
}
2. 错误处理
try/catch 可以捕获 await 的拒绝和函数中同步抛出的错误:
async function loadSafely() {
try {
const response = await fetch('/data.json')
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
console.error(error)
throw error
}
}
下面的 try/catch 不能捕获 Promise 异步回调中稍后发生的拒绝:
try {
fetchData().then(handleData)
} catch (error) {
// 只能捕获 fetchData() 同步抛出的错误,不能捕获后续 Promise 拒绝
}
如果使用 Promise 链,应在链尾 catch;如果使用 await,应在合适的边界使用 try/catch。不要为了“所有函数都包一层 catch”而吞掉错误。
3. 不要制造“async 地狱”
下面的变量名覆盖了同名函数,是错误示例:
// 错误:局部变量 getList 覆盖了要调用的函数
// const getList = await getList()
如果两个请求互不依赖,不要连续等待:
async function loadBoth() {
const listPromise = getList()
const anotherListPromise = getAnotherList()
const [list, anotherList] = await Promise.all([
listPromise,
anotherListPromise
])
return { list, anotherList }
}
也可以直接写成:
const [list, anotherList] = await Promise.all([
getList(),
getAnotherList()
])
Promise.all() 会并发启动输入操作,并在任意一个拒绝时拒绝结果 Promise;它不会取消已经启动的其他操作。如果业务需要全部结果,可以使用 Promise.allSettled();如果需要第一个成功结果,可以考虑 Promise.any()。
9. class
ES2015 的 class 提供了更清晰的类和继承语法。它不是传统构造函数的简单文本替换:类方法不可枚举,类构造器必须使用 new 调用,类体默认处于严格模式。
class Foo {
static bar() {
return this.baz()
}
static baz() {
return 'hello'
}
baz() {
return 'world'
}
}
console.log(Foo.bar()) // hello
console.log(new Foo().baz()) // world
原型方法不会复制到每个实例:
class Shape {
constructor(width, height) {
this.width = width
this.height = height
}
get area() {
return this.width * this.height
}
}
const square = new Shape(10, 10)
console.log(square.area) // 100
console.log(Object.hasOwn(square, 'area')) // false,getter 在原型上
现代 JavaScript 还支持公共/私有类字段和私有方法:
class Counter {
#value = 0
increment() {
this.#value += 1
return this.#value
}
}
#value 是真正的私有品牌检查,不等同于约定俗成的 _value,也不等同于 Symbol。
10. Decorator(装饰器)
原文把装饰器放在“ES6 使用手册”中,但装饰器不是 ES2015 特性。装饰器提案仍在 TC39 迭代,当前工具链中的旧版 Babel/TypeScript 装饰器和标准装饰器语义也不完全相同。
下面的代码应看作提案/工具链示意,不能默认在浏览器原生 JavaScript 中运行:
@log
class MathBox {
add(a, b) {
return a + b
}
}
在不使用装饰器语法时,可以用普通高阶函数实现函数装饰模式:
function logCall(fn) {
return function (...args) {
const result = fn.apply(this, args)
console.log({ args, result })
return result
}
}
const add = logCall((a, b) => a + b)
console.log(add(1, 2)) // 3
如果项目确实使用装饰器,应明确:
- 使用的是 TypeScript、Babel legacy decorators 还是当前标准提案实现;
- 构建工具如何配置;
- 输出目标是否需要转译;
- 装饰器的初始化顺序、
this、私有元素和类字段语义是否符合项目预期。
React 的 connect 也可以用普通函数包裹组件:
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent)
@connect 只是另一种工具链语法,不能说明装饰器已经成为 ES6 原生能力。
11. 函数参数默认值
参数默认值只在实参为 undefined 时生效,不会因为传入 0、空字符串、false 或 null 而生效:
function createUser(name = '匿名', age = 0) {
return { name, age }
}
console.log(createUser(undefined, 0)) // { name: '匿名', age: 0 }
console.log(createUser(null, null)) // { name: null, age: null }
原来的 quantity || 1 会把 0 错误地替换成 1:
function test(quantity) {
const q = quantity ?? 1
return q
}
解构参数和默认对象
function doSomething({
foo = 'Hi',
bar = 'Yo!',
baz = 13
} = {}) {
return { foo, bar, baz }
}
console.log(doSomething())
console.log(doSomething({ foo: '', baz: 0 }))
这里对象参数本身为 undefined 时使用 {};属性值为 null 时不会触发属性默认值。若要把 null 也视为空值,需要显式使用 ??。
React/JSX 示例应避免把已经接收的变量写成不存在的 classname:
const Button = ({ className = 'default-size' }) => (
<span className={className}>Button</span>
)
12. Rest 和 Spread(剩余参数与展开语法)
... 在不同位置有不同名字和语义:
- 函数参数位置:rest parameter,收集剩余参数;
- 函数调用和数组字面量位置:array spread,展开可迭代对象;
- 对象字面量位置:object spread,复制自身可枚举字符串/Symbol 属性。
1. 把 arguments 转成数组
function sortNumbers(...numbers) {
return numbers.sort((a, b) => a - b)
}
console.log(sortNumbers(10, 2, 1)) // [1, 2, 10]
rest 参数是真正的数组;arguments 是另一个类数组对象,不能把两者混为一谈。
2. 调用参数
function sum(a, b, c) {
return a + b + c
}
const numbers = [1, 2, 3]
console.log(sum(...numbers)) // 6
console.log(Math.max(...[14, 3, 77])) // 77
展开元素数量很大时要留意引擎的参数数量限制;需要处理任意长度数据时,使用循环或 reduce 通常更稳妥。
3. 构造和复制数组
const students = ['Jine', 'Tom']
const persons = ['Tony', ...students, 'Aaron']
const copy = [...persons]
const merged = [...[0, 1], ...[2, 3]]
数组展开是浅拷贝:
const source = [{ id: 1 }]
const copy = [...source]
copy[0].id = 2
console.log(source[0].id) // 2
4. 对象展开
对象展开在 ES2018 标准化:
const obj1 = { foo: 'bar', x: 42 }
const obj2 = { foo: 'baz', y: 13 }
const cloned = { ...obj1 }
const merged = { ...obj1, ...obj2 }
console.log(merged) // { foo: 'baz', x: 42, y: 13 }
后面的属性会覆盖前面的同名属性。它只复制自身可枚举属性,是浅拷贝,不会复制原型、不可枚举属性或访问器的完整描述符。
5. 有条件地构造对象
原文使用 name && { name },但这种写法会丢掉空字符串、0 和 false。如果“是否传入”以 undefined 为判断标准,可以这样写:
function pick({ id, name, age }) {
return {
guid: id,
...(name !== undefined ? { name } : {}),
...(age !== undefined ? { age } : {})
}
}
6. React 中传递 props
const params = { value: 1, title: 'Hello' }
<CustomComponent {...params} />
JSX 的展开属性也只是把对象属性传入组件;不要把不必要的内部属性、事件或用户输入无选择地全部透传给 DOM。
13. 双冒号绑定运算符 ::(历史提案)
原文的下面语法来自已长期停滞/未标准化的 bind operator 提案:
foo::bar
foo::bar(...args)
它不是当前 ECMAScript 标准,不能直接用于普通浏览器或 Node.js。不要把它当作 ES6 语法。现代替代写法是:
const bound = bar.bind(foo)
const result = bar.call(foo, ...args)
如果只需要在回调中保留外层 this,通常使用箭头函数:
button.addEventListener('click', () => controller.handleClick())
14. 解构赋值
解构可以从数组或对象中提取值,并支持重命名、默认值和剩余属性。
1. 对象基本解构
const student = { name: 'Ming', age: 18, city: 'Shanghai' }
const { name, age, city } = student
const { name: studentName } = student
console.log(studentName) // Ming
函数参数也可以直接解构:
function renderUser({ name, active }) {
return `${name}: ${active ? 'active' : 'inactive'}`
}
如果参数可能缺失,给整个参数提供默认对象:
function renderUser({ name = '匿名' } = {}) {
return name
}
2. 数组解构和默认值
const values = ['one', 'two', 'three', 'four']
const [one, two, , four] = values
let a, b
;[a, b] = [1, 2]
;[a, b] = [b, a]
const [first = 'default', second = 'default'] = []
在没有声明变量的情况下进行赋值解构,开头的分号可以避免被上一行表达式的自动分号插入解析影响。更推荐把相关语句写在清晰的语句边界中。
3. 深度解构
const object = {
a: {
b: {
c: 1
}
}
}
const {
a: {
b: { c = '' } = {}
} = {}
} = object
默认值要与下一层结构类型匹配;用空字符串代替对象会让后续解构语义混乱。
4. 数组方法名称要准确
原文的 splite 是拼写错误,应为 split:
const locale = 'zh-CN'
const [language, country] = locale.split('-')
5. 只获取部分属性
function getPosition() {
return { left: 1, right: 2, top: 3, bottom: 4 }
}
const { left, right } = getPosition()
const [firstValue, , thirdValue] = [10, 20, 30]
15. 增强的对象字面量
1. 属性简写
const name = 'Ming'
const age = 18
const student = {
name,
age
}
2. 方法简写
const api = {
getUser(id) {
return { id }
}
}
3. 计算属性名
const key = 'a_b'
const object = {
[key]: 'value',
[`${key}_2`]: 'another value'
}
console.log(object.a_b) // value
16. 数组的扩展方法
1. keys()、entries()、values()
const array = ['a', , 'c']
console.log(Object.keys(array)) // ['0', '2'],跳过空槽
console.log([...array.keys()]) // [0, 1, 2],按索引生成
console.log([...array.entries()]) // [[0, 'a'], [1, undefined], [2, 'c']]
console.log([...array.values()]) // ['a', undefined, 'c']
数组迭代器会访问索引范围,因此稀疏数组中的空槽表现与 Object.keys() 不同。
2. includes()
includes 返回布尔值,并使用 SameValueZero,因此可以找到 NaN:
const fruits = ['apple', 'strawberry', 'cherry']
console.log(fruits.includes('apple')) // true
console.log([NaN].includes(NaN)) // true
它与 indexOf 不完全等价:indexOf 返回索引,找不到时返回 -1,并且不能找到 NaN。如果需要索引,继续使用 indexOf。
3. find() 和 findIndex()
const inventory = [
{ name: 'apples', quantity: 2 },
{ name: 'bananas', quantity: 0 },
{ name: 'cherries', quantity: 5 }
]
const cherries = inventory.find(item => item.name === 'cherries')
const cherryIndex = inventory.findIndex(item => item.name === 'cherries')
console.log(cherries) // { name: 'cherries', quantity: 5 }
console.log(cherryIndex) // 2
find 返回第一个匹配元素,找不到时返回 undefined;findIndex 返回索引,找不到时返回 -1。两者找到结果后会停止遍历。不要为了“性能”机械替换 filter:如果需要全部匹配元素,filter 才是正确表达。
17. Optional chaining(可选链,ES2020)
可选链在访问值为 null 或 undefined 的对象时短路:
const object = {
foo: { bar: { baz: 42 } }
}
console.log(object?.foo?.bar?.baz) // 42
console.log(object?.missing?.value) // undefined
也可以调用可能不存在的方法:
const maybeCallback = undefined
maybeCallback?.('value') // 不调用,结果为 undefined
注意:可选链不能让未声明的变量变得安全,exists?.() 中如果 exists 根本没有声明,仍然会抛 ReferenceError。应先声明变量,或访问对象属性。
可选链不能作为赋值目标:
let user = null
// user?.name = 'Alice' // SyntaxError
常与空值合并运算符一起使用:
const displayName = user?.profile?.name ?? '匿名'
18. Logical assignment operators(逻辑赋值,ES2021)
逻辑赋值包括 ||=、&&= 和 ??=:
let count = 0
count ||= 10 // count 为 falsy,因此变成 10
let enabled = true
enabled &&= false // enabled 为 truthy,因此变成 false
let label = null
label ??= 'default' // 只有 null/undefined 才赋值
x ||= y 大致等价于 x || (x = y),但左侧表达式只求值一次,并且右侧在不需要时不会求值:
const config = {
get value() {
console.log('get')
return 1
},
set value(value) {
console.log('set', value)
}
}
config.value ||= 2 // 只读取,不触发 setter
注意对象链可能为 null:
// obj.a.b ||= value 仍会在 obj.a 为 null 时抛错
// 可选链目前也不能直接作为赋值左值
逻辑赋值不是简单的“更短写法”,要先确定业务需要的是 falsy 语义还是 nullish 语义。
19. Nullish coalescing(空值合并,ES2020)
?? 只有在左侧是 null 或 undefined 时才使用右侧:
const count = 0
const text = ''
console.log(count ?? 42) // 0
console.log(text ?? 'default') // ''
console.log(null ?? 'default') // default
与 || 的区别:
console.log(0 || 42) // 42
console.log(0 ?? 42) // 0
?? 不能与 || 或 && 无括号混用,否则是语法错误:
const value = (a || b) ?? c
?? 也支持短路求值,左侧不是空值时不会执行右侧表达式。
20. Pipeline operator(管道操作符,提案)
原文的代码:
5 |> double |> increment |> double
不是当前 ECMAScript 标准。管道操作符仍是 TC39 提案,语法方案和占位符形式也可能变化,不能在未配置转译器的浏览器或 Node.js 中直接运行。
现在可以用普通函数嵌套、方法链或临时变量表达同样逻辑:
const double = value => value * 2
const increment = value => value + 1
const result = double(increment(double(5)))
console.log(result) // 22
可读性更重要时,临时变量并不是“落后写法”:
const doubled = double(5)
const incremented = increment(doubled)
const result = double(incremented)
如果项目使用 Babel 的管道提案插件,应在构建配置和代码注释中明确这是实验性语法,并锁定插件版本。
总结
ES2015 之后的 JavaScript 已经形成按年发布的标准。掌握新语法时,除了记住简写形式,还应理解:
const不等于深度不可变;- 箭头函数不等于普通函数的短写,尤其是
this、arguments和构造能力不同; for...of依赖迭代器协议;- 对象/数组展开是浅拷贝;
async/await建立在 Promise 之上,顺序等待和并发等待需要区分;||、??、默认参数和逻辑赋值处理的“空值”范围不同;- 装饰器和管道操作符属于提案或工具链能力,不能与正式标准混写;
- 是否能使用某个特性,要结合目标浏览器、Node.js 版本、构建工具和 polyfill 策略。