Chendi WuMediaBlog
Back to blog

ECMAScript

ECMAScript 历年特性演进与示例笔记。

2026-05-16
NotesJavaScript

ECMAScript 2026(ES17)

Note

截至 2026/05,以下为 TC39 Stage 4 候选特性。最终 spec 将于 2026/06 发布,届时清单可能有微调。

Error.isError()

跨 realm 安全地判断一个值是否为 Error 实例,比 instanceof Error 更可靠(iframe / worker / vm 边界都可用)。

console.log(Error.isError(new Error('boom'))) // true
console.log(Error.isError(new TypeError('x'))) // true
console.log(Error.isError({ message: 'fake' })) // false
console.log(Error.isError('error string')) // false

Symbol 作 WeakMap / WeakSet 键

之前只允许对象作为弱引用键,现在 Symbol(且必须是非 Registered 的唯一 Symbol)也可以。

const key = Symbol('user-id')
const map = new WeakMap()

map.set(key, { name: 'Alice' })
console.log(map.get(key)) // { name: 'Alice' }

// Registered symbol 仍不允许
// map.set(Symbol.for('shared'), 1)  // TypeError

Math.sumPrecise()

精确浮点求和,避免普通 reduce((a, b) => a + b) 在大量数字相加时的累计误差。

const xs = [0.1, 0.2, 0.3]

console.log(xs.reduce((a, b) => a + b)) // 0.6000000000000001
console.log(Math.sumPrecise(xs)) // 0.6

Decorator Metadata

装饰器可以通过 context.metadata(或类上的 [Symbol.metadata])共享元数据,便于框架做反射 / 注册。

function tagged(tag) {
  return function (_value, context) {
    context.metadata[context.name] = tag
  }
}

class Service {
  @tagged('GET')
  list() {}

  @tagged('POST')
  create() {}
}

console.log(Service[Symbol.metadata])
// { list: 'GET', create: 'POST' }

Atomics.pause()

在 spin-wait 循环里给 CPU 一个"减速"提示(对应 x86 PAUSE / ARM YIELD),降低能耗并改善超线程性能。

// 自旋等待某个共享内存值变为 1
const buf = new SharedArrayBuffer(4)
const i32 = new Int32Array(buf)

while (Atomics.load(i32, 0) !== 1) {
  Atomics.pause()
}

Source Phase Imports

import source 在"源码阶段"导入模块(不执行),常用于按需实例化 WebAssembly。

import source wasmModule from './math.wasm'

// wasmModule 是 WebAssembly.Module 实例,可以多次实例化
const instance1 = await WebAssembly.instantiate(wasmModule)
const instance2 = await WebAssembly.instantiate(wasmModule)

ECMAScript 2025(ES16)

Iterator Helpers

在迭代器上链式调用 map / filter / take / drop / flatMap / reduce / toArray / forEach / some / every / find,避免先转数组带来的内存开销。

const result = [1, 2, 3, 4, 5, 6, 7, 8]
  .values()
  .filter((x) => x % 2 === 0)
  .map((x) => x * 10)
  .take(3)
  .toArray()

console.log(result) // [20, 40, 60]

Iterator.from()

把任意可迭代对象包装成标准 Iterator,从而能用上一组 helper 方法。

function* range(start, end) {
  for (let i = start; i < end; i++) yield i
}

const sum = Iterator.from(range(1, 6)).reduce((a, b) => a + b, 0)

console.log(sum) // 15

Set 集合方法

给 Set 加上集合代数:交集、并集、差集、对称差,以及子集 / 超集 / 不相交判断。

const a = new Set([1, 2, 3])
const b = new Set([2, 3, 4])

a.intersection(b) // Set { 2, 3 }
a.union(b) // Set { 1, 2, 3, 4 }
a.difference(b) // Set { 1 }
a.symmetricDifference(b) // Set { 1, 4 }

a.isSubsetOf(b) // false
a.isSupersetOf(b) // false
a.isDisjointFrom(b) // false

Promise.try()

统一处理同步抛错与异步异常 —— 把任意函数(不论同步异步、抛错 throw 还是 reject)包装成一个 promise。

function maybeAsync(value) {
  if (value < 0) throw new Error('invalid')
  return value > 10 ? Promise.resolve(value) : value
}

Promise.try(() => maybeAsync(20))
  .then((v) => console.log(v)) // 20
  .catch((e) => console.error(e))

Promise.try(() => maybeAsync(-1))
  .then((v) => console.log(v))
  .catch((e) => console.error(e.message)) // 'invalid'

RegExp.escape()

把字符串里的正则元字符转义,便于把用户输入安全地拼进正则。

const input = 'hello.world+?'
const re = new RegExp(RegExp.escape(input))

console.log(re.test('hello.world+?')) // true
console.log(re.source) // 'hello\\.world\\+\\?'

Import Attributes

用 with { type: ... } 显式声明被导入资源的类型,配合 JSON Modules 等。

import data from './data.json' with { type: 'json' }
import styles from './styles.css' with { type: 'css' }

// 动态 import 也支持
const config = await import('./config.json', { with: { type: 'json' } })

Explicit Resource Management

通过 using / await using 声明,配合 [Symbol.dispose] / [Symbol.asyncDispose],让资源在离开作用域时自动释放。

using 声明

function openFile(path) {
  const handle = { path, closed: false }
  return {
    handle,
    [Symbol.dispose]() {
      handle.closed = true
      console.log(`closed ${path}`)
    },
  }
}

{
  using file = openFile('a.txt')
  // ...使用 file
} // 离开块时自动调用 file[Symbol.dispose]()

await using

async function withDb() {
  await using db = await openDB()
  await db.query('SELECT 1')
} // 退出时自动 await db[Symbol.asyncDispose]()

DisposableStack / AsyncDisposableStack

批量管理多个一次性资源,按 LIFO 顺序释放。

{
  using stack = new DisposableStack()
  const a = stack.use(openFile('a.txt'))
  const b = stack.use(openFile('b.txt'))
  // ...
} // 先释放 b,再释放 a

Float16Array

半精度(16-bit)浮点数组,常用于 ML / WebGPU / 图形场景,相比 Float32Array 内存占用减半。

const arr = new Float16Array([1.5, 2.25, 3.125])

console.log(arr[0]) // 1.5
console.log(Math.f16round(1.337)) // 1.337890625

const buf = new ArrayBuffer(8)
const view = new DataView(buf)
view.setFloat16(0, 3.14)
console.log(view.getFloat16(0)) // 3.140625

ECMAScript 2024(ES15)

Group By

Object.groupBy()

静态方法根据提供的回调函数返回的字符串值对给定可迭代对象中的元素进行分组。返回的对象具有每个组的单独属性,其中包含组中的元素的数组。

const inventory = [
  { name: 'asparagus', type: 'vegetables', quantity: 5 },
  { name: 'bananas', type: 'fruit', quantity: 0 },
  { name: 'goat', type: 'meat', quantity: 23 },
  { name: 'cherries', type: 'fruit', quantity: 5 },
  { name: 'fish', type: 'meat', quantity: 22 },
]

// 根据元素的 type 属性的值对元素进行分组
const result = Object.groupBy(inventory, ({ type }) => type)

console.log(result)

/* Result is:
{
  vegetables: [
    { name: 'asparagus', type: 'vegetables', quantity: 5 },
  ],
  fruit: [
    { name: 'bananas', type: 'fruit', quantity: 0 },
    { name: 'cherries', type: 'fruit', quantity: 5 }
  ],
  meat: [
    { name: 'goat', type: 'meat', quantity: 23 },
    { name: 'fish', type: 'meat', quantity: 22 }
  ]
}
*/
const inventory = [
  { name: 'asparagus', type: 'vegetables', quantity: 5 },
  { name: 'bananas', type: 'fruit', quantity: 0 },
  { name: 'goat', type: 'meat', quantity: 23 },
  { name: 'cherries', type: 'fruit', quantity: 5 },
  { name: 'fish', type: 'meat', quantity: 22 },
]

// 根据 quantity 字段的值将项目分为不同分组(sufficient/restock)。
function myCallback({ quantity }) {
  return quantity > 5 ? 'sufficient' : 'restock'
}

const result2 = Object.groupBy(inventory, myCallback)

console.log(result)

/* Result is:
{
  restock: [
    { name: 'asparagus', type: 'vegetables', quantity: 5 },
    { name: 'bananas', type: 'fruit', quantity: 0 },
    { name: 'cherries', type: 'fruit', quantity: 5 }
  ],
  sufficient: [
    { name: 'goat', type: 'meat', quantity: 23 },
    { name: 'fish', type: 'meat', quantity: 22 }
  ]
}
*/

Map.groupBy()

Map.groupBy() 静态方法使用提供的回调函数返回的值对给定可迭代对象中的元素进行分组。最终返回的 Map 使用测试函数返回的唯一值作为键,可用于获取每个组中的元素组成的数组。

该方法主要用于对与对象相关的元素进行分组,特别是当该对象可能随时间而变化时。如果对象不变,你可以使用字符串表示它,并使用 Object.groupBy() 分组元素。

const inventory = [
  { name: 'asparagus', type: 'vegetables', quantity: 5 },
  { name: 'bananas', type: 'fruit', quantity: 0 },
  { name: 'goat', type: 'meat', quantity: 23 },
  { name: 'cherries', type: 'fruit', quantity: 5 },
  { name: 'fish', type: 'meat', quantity: 22 },
]

const restock = { restock: true }
const sufficient = { restock: false }
const result = Map.groupBy(inventory, ({ quantity }) =>
  quantity < 6 ? restock : sufficient,
)

console.log(result.get(restock))

/* Result is:
[
  { name: 'asparagus', type: 'vegetables', quantity: 5 },
  { name: 'bananas', type: 'fruit', quantity: 0 },
  { name: 'cherries', type: 'fruit', quantity: 5 },
]
*/

console.log(result.get(sufficient))

/* Result is:
[
  { name: 'goat', type: 'meat', quantity: 23 },
  { name: 'fish', type: 'meat', quantity: 22 },
]
*/

Promise.withResolvers()

Promise.withResolvers() 静态方法返回一个对象,其包含一个新的 Promise 对象和两个函数,用于解决或拒绝它,对应于传入给 Promise() 构造函数执行器的两个参数。

const { promise, resolve, reject } = Promise.withResolvers()

RegExp.prototype.unicodeSets

REGEXP 实例的 UnicoDesets 访问者属性返回是否与此正则表达式一起使用V标志。

  • https://github.com/tc39/proposal-regexp-v-flag#summary
  • https://babeljs.io/blog/2022/02/02/7.17.0
  • https://v8.dev/features/regexp-v-flag
  • 如果使用 v 标志, RegExp.prototype.unicodeSets 的值为 true ;否则 false
// eslint-disable-next-line prefer-regex-literals
const regex1 = new RegExp(/cdcdcd/v)
// eslint-disable-next-line prefer-regex-literals
const regex2 = new RegExp(/cdcdcd/)

console.log(regex1.unicodeSets)
// Expected output: true

console.log(regex2.unicodeSets)
// Expected output: false
// eslint-disable-next-line prefer-regex-literals
const regex1 = new RegExp('[\\p{Lowercase}&&\\p{Script=Greek}]')
// eslint-disable-next-line prefer-regex-literals
const regex2 = new RegExp('[\\p{Lowercase}&&\\p{Script=Greek}]', 'v')

console.log(regex1.unicodeSets)
// Expected output: false

console.log(regex2.unicodeSets)
// Expected output: true
console.log(/^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/u.test('cdhj996@gmail.com')) // true
console.log(/^[a-zA-Z0-9+_.\-]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/v.test('cdhj996@gmail.com')) // true

ArrayBuffers

  • 调整大小
// 不允许超过预先定义的 maxByteLength, resize 可增大也可以减小
const buffer = new ArrayBuffer(2, { maxByteLength: 32 })

// byteLength 属性是一个访问器属性,它的 set 访问器函数是 undefined,这意味着你只能读取这个属性。
// 该值在数组创建时确定,并且无法修改。
// 如果这个 ArrayBuffer 被分离,则此属性返回 0。
console.log(buffer.byteLength)
// Expected output: 2

console.log(buffer.resize(12))
// Expected output: undefined

console.log(buffer.byteLength)
// Expected output: 12

console.log(buffer.resize(100))
// Uncaught RangeError: ArrayBuffer.prototype.resize: Invalid length parameter
//     at ArrayBuffer.resize (<anonymous>)
//     at <anonymous>:10:20
  • 转移
const original = new ArrayBuffer(16)
const transferred = original.transfer()

// detached 属性是一个访问器属性,其 set 访问器函数是 undefined,这意味着你只能读取此属性。
// 该属性的值在创建 ArrayBuffer 时设置为 false。
// 如果 ArrayBuffer 已被传输,则该值将变为 true,这将使该实例从其底层内存中分离。
// 一旦缓冲区被分离,它就不再可用。
console.log(original.detached)
// Expected output: true

console.log(transferred.detached)
// Expected output: false

SharedArrayBuffers

SharedArrayBuffers 可以调整大小,但只能增长而不能缩小。 它们不可转移,因此无法获取 ArrayBuffers 所获取的方法 .transfer() 。

const sab = new SharedArrayBuffer(1024)
worker.postMessage(sab)

String

isWellFormed

isWellFormed() 能够测试一个字符串是否是格式正确的 (即不包含单独代理项)

Tip

“单独代理项(lone surrogate)”是指满足以下描述之一的 16 位码元:

  • 它在范围 0xD800 到 0xDBFF 内(含)(即为前导代理),但它是字符串中的最后一个码元,或者下一个码元不是后尾代理。
  • 它在范围 0xDC00 到 0xDFFF 内(含)(即为后尾代理),但它是字符串中的第一个码元,或者前一个码元不是前导代理。
const strings = [
  // 单独的前导代理
  'ab\uD800',
  'ab\uD800c',
  // 单独的后尾代理
  '\uDFFFab',
  'c\uDFFFab',
  // 格式正确
  'abc',
  'ab\uD83D\uDE04c',
]

for (const str of strings) {
  console.log(str.isWellFormed())
}

// Logs:
// false
// false
// false
// false
// true
// true

toWellFormed

toWellFormed() 方法返回一个字符串,其中该字符串的所有单独代理项都被替换为 Unicode 替换字符 U+FFFD

const strings = [
  // 单独的前导代理
  'ab\uD800',
  'ab\uD800c',
  // 单独的后尾代理
  '\uDFFFab',
  'c\uDFFFab',
  // 格式正确
  'abc',
  'ab\uD83D\uDE04c',
]

for (const str of strings) {
  console.log(str.toWellFormed())
}

// Logs:
// "ab�"
// "ab�c"
// "�ab"
// "c�ab"
// "abc"
// "ab😄c"

Atomics.waitAsync()

Atomics.waitAsync() 静态方法异步等待共享内存的特定位置并返回一个 Promise。

与 Atomics.wait() 不同,waitAsync 是非阻塞的且可用于主线程。

const sab = new SharedArrayBuffer(1024)
const int32 = new Int32Array(sab)

// 令一个读取线程休眠并在位置 0 处等待,预期该位置的值为 0。result.value 将是一个 promise。
const result = Atomics.waitAsync(int32, 0, 0, 1000)
// { async: true, value: Promise {<pending>} }

// 在该读取线程或另一个线程中,对内存位置 0 调用以令该 promise 解决为 "ok"。
Atomics.notify(int32, 0)
// { async: true, value: Promise {<fulfilled>: 'ok'} }

// 如果它没有解决为 "ok",则共享内存该位置的值不符合预期(value 将是 "not-equal" 而不是一个 promise)或已经超时(该 promise 将解决为 "time-out")。

ECMAScript 2023(ES14)

Array.prototype.toSorted

方法返回一个新数组,其中的元素根据提供的比较函数排序。它允许您根据需要自定义排序顺序。此方法不会改变原始数组。

// toSorted has the same signature as sort, but it creates a new array instead of operating on the array itself.
const arr = [5, 4, 2, 3, 1]
console.log(arr.sort()) // [1, 2, 3, 4, 5]
console.log(arr.toSorted()) // [1, 2, 3, 4, 5]
// toSorted also accepts a single optional argument, a comparator function.
// For example, we could use to create a new array in descending order,
const numbers = [10, 5, 2, 7, 3, 9, 1, 6, 4]
const sortedNumbers = numbers.toSorted((a, b) => {
  return b - a
})
console.log(sortedNumbers) // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// Also, note that this can be applied to arrays of objects.
// In such cases, you must provide a comparator function that utilizes the data within the objects since there is no inherent ordering for objects.
const objects = [
  { name: 'John', age: 30 },
  { name: 'Jane', age: 25 },
  { name: 'Bill', age: 40 },
  { name: 'Mary', age: 20 },
]
const sortedObjects = objects.toSorted((a, b) => {
  return a.name.localeCompare(b.name)
})
console.log(sortedObjects)
// [{"name":"Bill","age":40},{"name":"Jane","age":25},{"name":"John","age":30},{"name":"Mary","age":20}]

Array.prototype.toReversed

该方法返回一个新数组,其中元素的顺序相反。它不会改变原始数组。

const array = [1, 2, 3]
console.log(array.toReversed()) // [3, 2, 1]
console.log(array) // [1, 2, 3]

Array.prototype.with

with() 方法允许你根据索引修改单个元素,并返回一个新的数组。

const array = ['I', 'am', 'the', 'cd']

// Replace the string "cd" with "wd".
const newArray = array.with(3, 'wd')

console.log(newArray) // ['I', 'am', 'the', 'wd']
console.log(array) // ['I', 'am', 'the', 'cd']

Array.prototype.findLast

方法反向迭代数组,并返回满足提供的测试函数的第一个元素的值。否则返回 undefined。

const array = [5, 12, 50, 100, 44]
console.log(array.findLast((element) => element > 45)) // 100

console.log(array.find((element) => element > 45)) // 50

Array.prototype.findLastIndex

反向迭代数组,并返回满足所提供的测试函数的第一个元素的索引。若没有找到对应元素,则返回 -1。

const array = [5, 12, 50, 100, 44]
console.log(array.findLastIndex((element) => element > 45)) // 3

console.log(array.findIndex((element) => element > 45)) // 2

Array.prototype.toSpliced

// 通过 toSpliced() 来删除、添加和替换数组中的元素,并生成一个新的数组
// 比使用 slice() 和 concat() 更高效

const months = ['Jan', 'Mar', 'Apr', 'May']

// Inserting an element at index 1
const months2 = months.toSpliced(1, 0, 'Feb')
console.log(months2) // ["Jan", "Feb", "Mar", "Apr", "May"]

// Deleting two elements starting from index 2
const months3 = months2.toSpliced(2, 2)
console.log(months3) // ["Jan", "Feb", "May"]

// Replacing one element at index 1 with two new elements
const months4 = months3.toSpliced(1, 1, 'Feb', 'Mar')
console.log(months4) // ["Jan", "Feb", "Mar", "May"]

// Original array is not modified
console.log(months) // ["Jan", "Mar", "Apr", "May"]
// The toSpliced() method always creates a dense array.

// eslint-disable-next-line no-sparse-arrays
const arr = [1, , 3, 4, , 6]
console.log(arr.toSpliced(1, 2)) // [1, 4, undefined, 6]
// The toSpliced() method reads the length property of this.
// It then reads the integer-keyed properties needed and writes them into the new array.

const arrayLike = {
  length: 3,
  unrelated: 'foo',
  0: 5,
  2: 4,
}
console.log(Array.prototype.toSpliced.call(arrayLike, 0, 1, 2, 3))
// [2, 3, undefined, 4]

Hasbang Grammar

在终端可以直接执行 JavaScript 文件,而不需要使用 node 命令。

#!/usr/bin/env node

console.log("Hello, world!");
$ ./hello.js
Hello, world!

Symbols as WeakMap Keys

Symbols 数据类型可用作 WeakMap 中的 key,在之前,只有字符串和对象可以作为 WeakMap 的 key。

code source

const map = new WeakMap() // create a weak map

function useSymbol(symbol) {
  doSomethingWith(symbol)

  let called = map.get(symbol) || 0
  called++ // called one more time

  if (called > 2) console.log('Call more than twice')

  map.set(symbol, called)
}

const mySymbol = Symbol('FooBar')
useSymbol(mySymbol)
useSymbol(mySymbol)
useSymbol(mySymbol)

// mySymbol = null;
// eslint-disable-next-line no-delete-var
delete mySymbol // No live references are left to mySymbol, so we can count on the garbage collector eliminating the entry in the weakMap when it runs (eventually)

ECMAScript 2022(ES13)

Class Public Instance Fields

公共实例字段允许在类中直接声明属性,无需在构造函数中初始化。

{
  class ES13Class {
    name = 'wudi'
    age: unknown
    sex: unknown
  }

  const es13class = new ES13Class()

  console.log(es13class.name) // 'wudi'
  console.log(es13class.age) // undefined
  console.log(es13class.sex) // undefined
}

Private Instance Fields

使用 # 前缀声明私有字段,只能在类内部访问。

{
  class ES13Class {
    #name: string // 私有字段
    #age = 18

    constructor(name: string) {
      this.#name = name
    }

    getName() {
      return this.#name
    }

    getAge() {
      return this.#age
    }
  }

  const es13class = new ES13Class('wudi')

  console.log(es13class.getName()) // 'wudi'
  console.log(es13class.getAge()) // 18
  
  // console.log(es13class.#name) // SyntaxError: Private field '#name' must be declared in an enclosing class
}

Private Methods and Accessors

私有方法和访问器只能在类内部调用。

{
  class ES13Class {
    #name: string

    constructor(name: string) {
      this.#name = name
    }

    // 私有方法
    #formatName() {
      return `My name is: ${this.#name}`
    }

    // 私有 getter
    get #privateAge() {
      return 18
    }

    // 私有 setter
    set #privateName(value: string) {
      this.#name = value
    }

    getFormattedName() {
      return this.#formatName()
    }
  }

  const es13class = new ES13Class('wudi')
  console.log(es13class.getFormattedName()) // 'My name is: wudi'
  
  // console.log(es13class.#formatName()) // SyntaxError: Private field '#formatName' must be declared in an enclosing class
}

Static Class Fields and Methods

静态字段和方法属于类本身,而不是实例。

{
  class ES13Class {
    static baseNum = 100
    static #privateStaticNum = 200

    // 静态方法
    static getBaseNum() {
      return this.baseNum
    }

    // 静态方法访问私有静态字段
    static getPrivateNum() {
      return this.#privateStaticNum
    }

    // 静态箭头函数
    static doubleBaseNum = () => this.baseNum * 2
  }

  console.log(ES13Class.baseNum) // 100
  console.log(ES13Class.getBaseNum()) // 100
  console.log(ES13Class.getPrivateNum()) // 200
  console.log(ES13Class.doubleBaseNum()) // 200

  // 实例无法访问静态成员
  const instance = new ES13Class()
  // console.log(instance.baseNum) // undefined
}

Class Static Block

类静态初始化块在类定义时执行,用于初始化静态字段或执行静态初始化逻辑。

{
  let getPrivateData: (obj: any) => any
  
  class ES13Class {
    static config = 'default'
    static #privateData = []

    #instanceData

    constructor(data: any) {
      this.#instanceData = { data }
    }

    static {
      // 静态初始化块
      console.log('Class is being initialized')
      this.#privateData = ['item1', 'item2']
      
      // 导出访问私有数据的函数
      getPrivateData = (obj) => {
        if (!(obj instanceof ES13Class)) {
          throw new Error('Invalid object')
        }
        return obj.#instanceData
      }
    }

    static {
      // 可以有多个静态块
      this.config = 'initialized'
    }
  }

  console.log(ES13Class.config) // 'initialized'
  
  const instance = new ES13Class({ value: 42 })
  console.log(getPrivateData(instance)) // { data: { value: 42 } }
}

Ergonomic Brand Checks for Private Fields

使用 in 操作符检查对象是否包含特定的私有字段。

{
  class ES13Class {
    #brand = 'ES13'

    static hasValidBrand(obj: any) {
      // 检查对象是否包含私有字段 #brand
      return #brand in obj
    }

    static getBrand(obj: any) {
      if (!(#brand in obj)) {
        throw new Error('Object does not have valid brand')
      }
      return obj.#brand
    }
  }

  const validInstance = new ES13Class()
  const invalidObject = {}

  console.log(ES13Class.hasValidBrand(validInstance)) // true
  console.log(ES13Class.hasValidBrand(invalidObject)) // false

  console.log(ES13Class.getBrand(validInstance)) // 'ES13'
  // console.log(ES13Class.getBrand(invalidObject)) // Error: Object does not have valid brand
}

Object.hasOwn()

Object.hasOwn() 作为 Object.prototype.hasOwnProperty() 的更安全替代方案。

{
  const obj = {
    name: 'wudi',
    age: 18
  }

  // 传统方式
  console.log(Object.prototype.hasOwnProperty.call(obj, 'name')) // true

  // 新方式 - 更简洁安全
  console.log(Object.hasOwn(obj, 'name')) // true
  console.log(Object.hasOwn(obj, 'height')) // false

  // 处理 null 原型对象
  const nullObj = Object.create(null)
  nullObj.prop = 'value'
  
  // console.log(nullObj.hasOwnProperty('prop')) // TypeError: nullObj.hasOwnProperty is not a function
  console.log(Object.hasOwn(nullObj, 'prop')) // true
}

Array.prototype.at()

使用 at() 方法进行反向索引,支持负数索引。

{
  const arr = ['a', 'b', 'c', 'd', 'e']

  // 传统方式获取最后一个元素
  console.log(arr[arr.length - 1]) // 'e'
  
  // 使用 at() 方法
  console.log(arr.at(-1)) // 'e'
  console.log(arr.at(-2)) // 'd'
  console.log(arr.at(0)) // 'a'
  console.log(arr.at(1)) // 'b'

  // 超出范围返回 undefined
  console.log(arr.at(10)) // undefined
  console.log(arr.at(-10)) // undefined

  // 链式调用中的便利性
  const result = [1, 2, 3]
    .map(x => x * 2)
    .filter(x => x > 2)
    .at(-1) // 获取最后一个元素
  
  console.log(result) // 6
}

Error Cause

为 Error 对象添加 cause 属性,用于错误链追踪。

{
  function parseData(data: string) {
    try {
      return JSON.parse(data)
    } catch (err) {
      throw new Error('Failed to parse data', { cause: err })
    }
  }

  function processFile(filename: string) {
    try {
      // 模拟文件读取错误
      throw new Error(`File not found: ${filename}`)
    } catch (err) {
      throw new Error('Failed to process file', { cause: err })
    }
  }

  try {
    processFile('config.json')
  } catch (error) {
    console.log(error.message) // 'Failed to process file'
    console.log(error.cause?.message) // 'File not found: config.json'
    
    // 错误链追踪
    let currentError = error
    while (currentError) {
      console.log('Error:', currentError.message)
      currentError = currentError.cause
    }
  }
}

RegExp Match Indices

正则表达式新增 d 标志,返回匹配的索引信息。

{
  const regex = /t(e)(st(\d?))/d
  const str = 'test1test2'
  
  const match = regex.exec(str)
  
  console.log(match[0]) // 'test1'
  console.log(match.indices[0]) // [0, 5] - 整个匹配的位置
  console.log(match.indices[1]) // [1, 2] - 第一个捕获组 'e' 的位置
  console.log(match.indices[2]) // [2, 5] - 第二个捕获组 'st1' 的位置
  console.log(match.indices[3]) // [4, 5] - 第三个捕获组 '1' 的位置

  // 命名捕获组的索引
  const namedRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/d
  const dateStr = '2022-12-25'
  const dateMatch = namedRegex.exec(dateStr)
  
  console.log(dateMatch.indices.groups.year) // [0, 4]
  console.log(dateMatch.indices.groups.month) // [5, 7]
  console.log(dateMatch.indices.groups.day) // [8, 10]
}

Top-level await

在模块的顶层直接使用 await,无需包装在 async 函数中。

{
  // 在模块顶层使用 await
  const fetchData = () => {
    return new Promise((resolve) => {
      setTimeout(() => resolve('Data loaded'), 1000)
    })
  }

  // 顶层 await
  const data = await fetchData()
  console.log(data) // 'Data loaded'

  // 动态导入
  const { default: moment } = await import('moment')
  
  // 条件性导入
  const locale = 'zh-cn'
  const localeModule = await import(`./locales/${locale}.js`)

  // 异步配置初始化
  const config = await fetch('/api/config').then(res => res.json())
  console.log('Config loaded:', config)
}

// 注意:需要在模块环境中使用,文件需要有 import/export 或添加 export {}
export {}

ECMAScript 2021(ES12)

Logical Assignment Operators

&&= (逻辑与赋值)

只有当左侧操作数为真值时,才会进行赋值操作。

{
  let num1 = 5
  let num2 = 10

  num1 &&= num2
  console.log(num1) // 10

  let num3 = 0
  num3 &&= 99
  console.log(num3) // 0 (因为 0 是假值,不会赋值)

  // 等价于
  // num1 && (num1 = num2)
  // if (num1) {
  //   num1 = num2
  // }
}

||= (逻辑或赋值)

只有当左侧操作数为假值时,才会进行赋值操作。

{
  let num1
  let num2 = 10

  num1 ||= num2
  console.log(num1) // 10

  let num3 = 'existing'
  num3 ||= 'default'
  console.log(num3) // 'existing' (已有值,不会被替换)

  // 等价于
  // num1 || (num1 = num2)
  // if (!num1) {
  //   num1 = num2
  // }

  // 实际应用:设置默认值
  let config = {}
  config.timeout ||= 5000
  config.retries ||= 3
  console.log(config) // { timeout: 5000, retries: 3 }
}

??= (空值合并赋值)

只有当左侧操作数为 null 或 undefined 时,才会进行赋值操作。

{
  let num1
  let num2 = 10

  num1 ??= num2
  console.log(num1) // 10

  // 与 ||= 的区别
  let num3 = false
  num3 ??= num2
  console.log(num3) // false (false 不是 null 或 undefined,不会赋值)

  let num4 = null
  num4 ??= 123
  console.log(num4) // 123

  // 等价于
  // num1 ?? (num1 = num2)

  // 实际应用:保留 0、false、'' 等假值
  let settings = {
    volume: 0,
    enabled: false,
    theme: null
  }
  
  settings.volume ??= 50    // 保持 0
  settings.enabled ??= true // 保持 false
  settings.theme ??= 'dark' // 设置为 'dark'
  
  console.log(settings) // { volume: 0, enabled: false, theme: 'dark' }
}

Numeric Separators

使用下划线 _ 作为数字分隔符,提高大数字的可读性。

{
  // 整数
  const million = 1_000_000
  const billion = 1_000_000_000
  
  console.log(million) // 1000000
  console.log(billion) // 1000000000

  // 小数
  const decimal = 1000.12_34_56
  console.log(decimal) // 1000.123456

  // 二进制、八进制、十六进制
  const binary = 0b1010_0001_1000_0101
  const octal = 0o777_444
  const hex = 0xFF_EC_DE_5E

  console.log(binary) // 41349
  console.log(octal) // 261924
  console.log(hex) // 4293837150

  // 科学计数法
  const scientific = 1.234_567e8
  console.log(scientific) // 123456700

  // 注意:分隔符不影响实际值,只是视觉上的分组
  console.log(1_000 === 1000) // true
}

Promise.any()

返回第一个成功(fulfilled)的 Promise,所有 Promise 都失败时才会拒绝。

{
  // 基本用法:返回最快成功的 Promise
  Promise.any([
    new Promise(resolve => setTimeout(() => resolve('Fast'), 100)),
    new Promise(resolve => setTimeout(() => resolve('Slow'), 500)),
    Promise.reject('Error')
  ])
  .then(result => console.log(result)) // 'Fast'

  // 与 Promise.race() 的区别
  Promise.race([
    Promise.reject('Error 1'),
    new Promise(resolve => setTimeout(() => resolve('Success'), 100))
  ])
  .catch(err => console.log('Race failed:', err)) // 'Race failed: Error 1'

  Promise.any([
    Promise.reject('Error 1'),
    new Promise(resolve => setTimeout(() => resolve('Success'), 100))
  ])
  .then(result => console.log('Any succeeded:', result)) // 'Any succeeded: Success'

  // 所有 Promise 都失败时返回 AggregateError
  Promise.any([
    Promise.reject('Error 1'),
    Promise.reject('Error 2'),
    Promise.reject('Error 3')
  ])
  .catch(err => {
    console.log(err.name) // 'AggregateError'
    console.log(err.errors) // ['Error 1', 'Error 2', 'Error 3']
  })

  // 实际应用:多个资源竞争加载
  async function loadResource() {
    try {
      const data = await Promise.any([
        fetch('/api/data-server1'),
        fetch('/api/data-server2'),
        fetch('/api/data-server3')
      ])
      return await data.json()
    } catch (error) {
      console.log('All servers failed:', error.errors)
      throw new Error('All servers are unavailable')
    }
  }
}

String.prototype.replaceAll()

替换字符串中所有匹配的模式,无需使用全局正则表达式。

{
  const str = 'I belong to ES12 new API, ES12 is great, ES12!'

  // 传统方法:只替换第一个匹配
  console.log(str.replace('ES12', 'ES2021')) 
  // 'I belong to ES2021 new API, ES12 is great, ES12!'

  // 传统方法:使用全局正则替换所有
  console.log(str.replace(/ES12/g, 'ES2021'))
  // 'I belong to ES2021 new API, ES2021 is great, ES2021!'

  // 新方法:replaceAll 直接替换所有
  console.log(str.replaceAll('ES12', 'ES2021'))
  // 'I belong to ES2021 new API, ES2021 is great, ES2021!'

  // 使用函数作为替换值
  const result = 'hello world hello'.replaceAll('hello', (match, offset) => {
    return `${match}(${offset})`
  })
  console.log(result) // 'hello(0) world hello(12)'

  // 使用正则表达式(必须带全局标志)
  console.log(str.replaceAll(/ES12/g, 'ES2021'))
  // 'I belong to ES2021 new API, ES2021 is great, ES2021!'

  // 错误用法:正则表达式不带全局标志会报错
  // console.log(str.replaceAll(/ES12/, 'ES2021'))
  // TypeError: String.prototype.replaceAll called with a non-global RegExp argument

  // 实际应用:安全地替换特殊字符
  const template = 'Hello {{name}}, welcome to {{place}}!'
  const filled = template
    .replaceAll('{{name}}', 'Alice')
    .replaceAll('{{place}}', 'Wonderland')
  console.log(filled) // 'Hello Alice, welcome to Wonderland!'
}

WeakRef

创建对对象的弱引用,不会阻止垃圾回收。

注意: 正确使用 WeakRef 需要仔细考虑,建议尽量避免使用。垃圾回收的时机和行为在不同 JavaScript 引擎中可能不同。

{
  // 基本用法
  const obj = { name: 'example', data: [1, 2, 3] }
  const weakRef = new WeakRef(obj)

  // 获取引用的对象
  const target = weakRef.deref()
  if (target) {
    console.log(target.name) // 'example'
  } else {
    console.log('Object has been garbage collected')
  }

  // 实际应用:缓存管理
  class Cache {
    constructor() {
      this.cache = new Map()
    }

    set(key, value) {
      this.cache.set(key, new WeakRef(value))
    }

    get(key) {
      const ref = this.cache.get(key)
      if (!ref) return undefined

      const value = ref.deref()
      if (value === undefined) {
        // 对象已被回收,清理缓存
        this.cache.delete(key)
        return undefined
      }
      return value
    }

    cleanup() {
      for (const [key, ref] of this.cache.entries()) {
        if (ref.deref() === undefined) {
          this.cache.delete(key)
        }
      }
    }
  }

  const cache = new Cache()
  let largeObject = { data: new Array(1000000).fill('data') }
  
  cache.set('large', largeObject)
  console.log(cache.get('large')) // 对象存在
  
  largeObject = null // 移除强引用
  // 在垃圾回收后,cache.get('large') 可能返回 undefined
}

FinalizationRegistry

当对象被垃圾回收时执行清理回调。

{
  // 创建清理注册表
  const registry = new FinalizationRegistry((heldValue) => {
    console.log(`Cleaning up: ${heldValue}`)
  })

  // 注册对象
  let obj = { name: 'test' }
  registry.register(obj, 'test-object')

  // 当 obj 被垃圾回收时,会执行回调

  // 实际应用:DOM 元素计数器示例
  class Counter {
    constructor(element) {
      this.ref = new WeakRef(element)
      this.count = 0
      
      // 注册清理
      const registry = new FinalizationRegistry(() => {
        console.log('Counter element was garbage collected')
        this.stop()
      })
      registry.register(element, undefined, this)
      
      this.start()
    }

    start() {
      if (this.timer) return

      const tick = () => {
        const element = this.ref.deref()
        if (element) {
          element.textContent = ++this.count
        } else {
          console.log('Element no longer exists')
          this.stop()
        }
      }

      tick()
      this.timer = setInterval(tick, 1000)
    }

    stop() {
      if (this.timer) {
        clearInterval(this.timer)
        this.timer = null
      }
    }
  }

  // 使用示例(在 DOM 环境中)
  /*
  const counterElement = document.createElement('div')
  document.body.appendChild(counterElement)
  
  const counter = new Counter(counterElement)
  
  // 5秒后移除元素
  setTimeout(() => {
    counterElement.remove()
  }, 5000)
  */
}

ECMAScript 2020(ES11)

Optional Chaining (?.)

安全地访问嵌套对象属性,避免因中间值为 null 或 undefined 导致的错误。

{
  const user = {
    name: 'Alice',
    address: {
      street: 'Main St',
      getNum() {
        return '123'
      },
      dataList: [
        { details: 'detail1' },
        { details: 'detail2' }
      ]
    }
  }

  // 传统方式 - 冗长且易出错
  const street = user && user.address && user.address.street
  const num = user && user.address && user.address.getNum && user.address.getNum()
  const details = user && user.address && user.address.dataList && 
                  user.address.dataList[0] && user.address.dataList[0].details

  // 使用可选链 - 简洁安全
  const street2 = user?.address?.street
  const num2 = user?.address?.getNum?.()
  const details2 = user?.address?.dataList?.[0]?.details

  console.log(street2) // 'Main St'
  console.log(num2) // '123'
  console.log(details2) // 'detail1'

  // 处理不存在的属性
  const nonExistent = user?.profile?.avatar?.url
  console.log(nonExistent) // undefined (而不是报错)

  // 动态属性访问
  const property = 'street'
  const dynamicValue = user?.address?.[property]
  console.log(dynamicValue) // 'Main St'
}

Nullish Coalescing (??)

空值合并运算符,只有当左侧为 null 或 undefined 时才使用右侧的默认值。

{
  // 与 || 的区别
  console.log('' || 'default')  // 'default'
  console.log('' ?? 'default')  // ''

  console.log(0 || 'default')   // 'default'
  console.log(0 ?? 'default')   // 0

  console.log(false || 'default') // 'default'
  console.log(false ?? 'default') // false

  console.log(null ?? 'default')      // 'default'
  console.log(undefined ?? 'default') // 'default'

  // 实际应用场景
  function processConfig(config) {
    // 保留有意义的假值,只处理真正的"空值"
    const timeout = config.timeout ?? 5000
    const retries = config.retries ?? 3
    const debug = config.debug ?? false
    
    return { timeout, retries, debug }
  }

  console.log(processConfig({ timeout: 0, debug: false }))
  // { timeout: 0, retries: 3, debug: false }
  
  console.log(processConfig({ timeout: null, debug: undefined }))
  // { timeout: 5000, retries: 3, debug: false }

  // 与可选链结合使用
  const userSettings = {
    theme: null,
    notifications: {
      email: false,
      push: undefined
    }
  }

  const theme = userSettings?.theme ?? 'light'
  const emailNotifs = userSettings?.notifications?.email ?? true
  const pushNotifs = userSettings?.notifications?.push ?? true

  console.log({ theme, emailNotifs, pushNotifs })
  // { theme: 'light', emailNotifs: false, pushNotifs: true }
}

BigInt

新的原始数据类型,用于表示任意精度的大整数。

{
  // JavaScript Number 类型的限制
  console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
  console.log(2 ** 53) // 9007199254740992
  console.log(2 ** 53 + 1) // 9007199254740992 (精度丢失!)

  // BigInt 可以表示任意大的整数
  const bigInt1 = 9007199254740993n
  const bigInt2 = BigInt(9007199254740993)
  const bigInt3 = BigInt('9007199254740994')

  console.log(bigInt1) // 9007199254740993n
  console.log(typeof bigInt1) // 'bigint'

  // BigInt 运算
  const result = 9007199254740991n + 2n
  console.log(result) // 9007199254740993n

  // 大数计算示例
  const factorial = (n) => {
    if (n <= 1n) return 1n
    return n * factorial(n - 1n)
  }
  console.log(factorial(25n)) // 15511210043330985984000000n

  // 比较操作
  console.log(1n == 1)   // true (类型转换)
  console.log(1n === 1)  // false (类型严格比较)
  console.log(1n < 2)    // true
  console.log(2n > 1)    // true

  // 注意事项
  // 1. 不能与 Number 混合运算
  // console.log(1n + 1) // TypeError

  // 2. 需要显式转换
  console.log(Number(1n) + 1) // 2
  console.log(1n + BigInt(1)) // 2n

  // 3. BigInt 不能使用 Math 对象的方法
  // console.log(Math.sqrt(4n)) // TypeError

  // 4. JSON.stringify 不直接支持 BigInt
  const obj = { bigNum: 123n }
  // console.log(JSON.stringify(obj)) // TypeError
  
  // 需要自定义序列化
  console.log(JSON.stringify(obj, (key, value) =>
    typeof value === 'bigint' ? value.toString() : value
  )) // '{"bigNum":"123"}'
}

globalThis

提供跨平台访问全局对象的标准方式。

{
  // 传统方式 - 平台兼容性差
  function getGlobal() {
    if (typeof self !== 'undefined') return self         // Web Workers
    if (typeof window !== 'undefined') return window     // 浏览器
    if (typeof global !== 'undefined') return global     // Node.js
    throw new Error('Unable to locate global object')
  }

  // 现代方式 - 统一的全局对象访问
  console.log(typeof globalThis) // 'object'

  // 在不同环境中都能正常工作
  globalThis.myGlobalVar = 'Hello World'
  console.log(globalThis.myGlobalVar) // 'Hello World'

  // 实际应用:polyfill 检测
  if (!globalThis.fetch) {
    // 在 Node.js 环境中添加 fetch polyfill
    globalThis.fetch = require('node-fetch')
  }

  // 检查全局 API 是否存在
  if (typeof globalThis.setTimeout === 'function') {
    console.log('Timer APIs available')
  }

  // 跨平台的全局变量设置
  globalThis.APP_CONFIG = {
    version: '1.0.0',
    env: 'production'
  }
}

Dynamic import()

动态导入模块,返回 Promise,支持按需加载。

{
  // 基本用法
  async function loadModule() {
    try {
      const module = await import('./utils.js')
      module.doSomething()
    } catch (error) {
      console.error('Failed to load module:', error)
    }
  }

  // 条件导入
  async function loadFeature(featureName) {
    const modulePath = `./features/${featureName}.js`
    const module = await import(modulePath)
    return module.default
  }

  // 与传统 import 语句的区别
  // 静态导入 (编译时)
  // import { helper } from './helper.js'

  // 动态导入 (运行时)
  const loadHelper = async () => {
    const { helper } = await import('./helper.js')
    return helper
  }

  // 实际应用:路由懒加载
  const routes = {
    '/home': () => import('./pages/Home.js'),
    '/about': () => import('./pages/About.js'),
    '/contact': () => import('./pages/Contact.js')
  }

  async function navigateTo(path) {
    const loadPage = routes[path]
    if (loadPage) {
      const pageModule = await loadPage()
      pageModule.default.render()
    }
  }

  // 代码分割示例
  button.addEventListener('click', async () => {
    const { heavyLibrary } = await import('./heavy-library.js')
    heavyLibrary.process()
  })

  // 动态导入 JSON
  async function loadConfig() {
    const config = await import('./config.json', {
      assert: { type: 'json' }
    })
    return config.default
  }
}

String.prototype.matchAll()

返回所有匹配正则表达式的迭代器,包含捕获组信息。

{
  const str = `
    <html>
      <body>
        <div>第一个div</div>
        <p>这是一个p</p>
        <span>span</span>
        <div>第二个div</div>
      </body>
    </html>
  `
  const regExp = /<div>(.*?)<\/div>/g

  // 传统方法1:使用 exec() 循环
  function selectDivExec(regExp, str) {
    const matches = []
    let match
    while ((match = regExp.exec(str)) !== null) {
      matches.push(match[1])
    }
    return matches
  }

  // 传统方法2:使用 replace()
  function selectDivReplace(regExp, str) {
    const matches = []
    str.replace(regExp, (all, first) => {
      matches.push(first)
    })
    return matches
  }

  // 现代方法:使用 matchAll()
  function selectDivMatchAll(regExp, str) {
    const matches = []
    for (const match of str.matchAll(regExp)) {
      matches.push(match[1])
    }
    return matches
  }

  console.log(selectDivExec(regExp, str))      // ['第一个div', '第二个div']
  console.log(selectDivReplace(regExp, str))   // ['第一个div', '第二个div']
  console.log(selectDivMatchAll(regExp, str))  // ['第一个div', '第二个div']

  // 更复杂的示例:解析 URL 参数
  const url = 'https://example.com?name=Alice&age=25&city=NewYork&name=Bob'
  const paramRegex = /([^&=]+)=([^&]*)/g

  // 使用 matchAll 获取所有参数
  const params = new Map()
  for (const match of url.matchAll(paramRegex)) {
    const [, key, value] = match
    if (params.has(key)) {
      // 处理重复参数
      const existing = params.get(key)
      params.set(key, Array.isArray(existing) ? [...existing, value] : [existing, value])
    } else {
      params.set(key, value)
    }
  }

  console.log(params)
  // Map(3) {
  //   'name' => ['Alice', 'Bob'],
  //   'age' => '25',
  //   'city' => 'NewYork'
  // }

  // 获取匹配的详细信息
  const text = 'The dates are 2023-12-25 and 2024-01-01'
  const dateRegex = /(\d{4})-(\d{2})-(\d{2})/g

  for (const match of text.matchAll(dateRegex)) {
    console.log(`Full match: ${match[0]}`)
    console.log(`Year: ${match[1]}, Month: ${match[2]}, Day: ${match[3]}`)
    console.log(`Index: ${match.index}`)
    console.log('---')
  }
}

Promise.allSettled()

等待所有 Promise 完成(无论成功或失败),返回所有结果的状态信息。

{
  // 与 Promise.all() 的对比
  const promises = [
    Promise.reject({ code: 500, msg: '服务异常' }),
    Promise.resolve({ code: 200, data: ['1', '2', '3'] }),
    Promise.resolve({ code: 200, data: ['4', '5', '6'] })
  ]

  // Promise.all() - 任一失败就整体失败
  Promise.all(promises)
    .then(res => console.log('All succeeded:', res))
    .catch(err => console.log('One failed:', err))
  // 输出: One failed: { code: 500, msg: '服务异常' }

  // Promise.allSettled() - 等待所有完成
  Promise.allSettled(promises)
    .then(results => {
      console.log('All settled:')
      results.forEach((result, index) => {
        if (result.status === 'fulfilled') {
          console.log(`Promise ${index} succeeded:`, result.value)
        } else {
          console.log(`Promise ${index} failed:`, result.reason)
        }
      })

      // 过滤成功的结果
      const successful = results
        .filter(result => result.status === 'fulfilled')
        .map(result => result.value)
      
      console.log('Successful results:', successful)
    })

  // 实际应用:批量 API 调用
  async function fetchMultipleAPIs() {
    const apiCalls = [
      fetch('/api/users'),
      fetch('/api/posts'),
      fetch('/api/comments')
    ]

    const results = await Promise.allSettled(apiCalls)
    
    const responses = {}
    results.forEach((result, index) => {
      const apiName = ['users', 'posts', 'comments'][index]
      
      if (result.status === 'fulfilled') {
        responses[apiName] = result.value
      } else {
        console.error(`Failed to fetch ${apiName}:`, result.reason)
        responses[apiName] = null
      }
    })

    return responses
  }

  // 批量文件上传示例
  async function uploadFiles(files) {
    const uploadPromises = files.map(file => 
      fetch('/upload', {
        method: 'POST',
        body: file
      })
    )

    const results = await Promise.allSettled(uploadPromises)
    
    const report = {
      successful: 0,
      failed: 0,
      errors: []
    }

    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        report.successful++
      } else {
        report.failed++
        report.errors.push({
          file: files[index].name,
          error: result.reason.message
        })
      }
    })

    return report
  }
}

ECMAScript 2019(ES10)

Optional catch binding

catch 子句可以省略参数,当不需要使用错误对象时更简洁。

{
  // 传统写法 - 必须提供参数
  function isValidJSONOld(text) {
    try {
      JSON.parse(text)
      return true
    } catch (error) {  // 即使不使用 error 也必须声明
      return false
    }
  }

  // ES10 新写法 - 可以省略参数
  function isValidJSON(text) {
    try {
      JSON.parse(text)
      return true
    } catch {  // 省略错误参数
      return false
    }
  }

  const validJson = '{ "key1": "value1", "key2": "value2" }'
  const invalidJson = '{ key1: value1 }'

  console.log(isValidJSON(validJson))   // true
  console.log(isValidJSON(invalidJson)) // false

  // 实际应用:功能检测
  function hasLocalStorage() {
    try {
      localStorage.setItem('test', 'test')
      localStorage.removeItem('test')
      return true
    } catch {
      return false
    }
  }

  // 检查是否支持某个 API
  function supportsWebP() {
    try {
      return document.createElement('canvas')
        .toDataURL('image/webp')
        .indexOf('data:image/webp') === 0
    } catch {
      return false
    }
  }
}

Array.prototype.flat()

按指定深度递归地将所有子数组连接,并返回一个新数组。

{
  // 基本用法
  const arr1 = [1, 2, [3, 4]]
  const arr2 = [1, 2, [3, 4, [5, 6]]]
  const arr3 = [1, 2, [3, 4, [5, 6, [7, 8]]]]

  console.log(arr1.flat())        // [1, 2, 3, 4]
  console.log(arr2.flat())        // [1, 2, 3, 4, [5, 6]] (默认深度为1)
  console.log(arr2.flat(2))       // [1, 2, 3, 4, 5, 6]
  console.log(arr3.flat(Infinity)) // [1, 2, 3, 4, 5, 6, 7, 8] (完全扁平化)

  // 处理稀疏数组
  const sparse = [1, 2, , 4, [5, , 7]]
  console.log(sparse.flat()) // [1, 2, 4, 5, 7] (移除空槽)

  // 实际应用:数据结构扁平化
  const categories = [
    ['EF', ['JavaScript', 'TypeScript', 'React']],
    ['BE', ['Node.js', 'Python', 'Java']],
    ['DB', ['MySQL', 'MongoDB']]
  ]

  const allTechnologies = categories
    .map(([category, techs]) => techs)
    .flat()
  
  console.log(allTechnologies)
  // ['JavaScript', 'TypeScript', 'React', 'Node.js', 'Python', 'Java', 'MySQL', 'MongoDB']

  // 处理嵌套的用户权限
  const userRoles = [
    'user',
    ['admin', ['read', 'write', ['delete', 'modify']]],
    'guest'
  ]

  const flatRoles = userRoles.flat(3)
  console.log(flatRoles) // ['user', 'admin', 'read', 'write', 'delete', 'modify', 'guest']
}

Array.prototype.flatMap()

首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。

{
  const numbers = [1, 2, 3, 4]

  // 对比 map() 和 flatMap()
  console.log(numbers.map(x => [x * 2]))      // [[2], [4], [6], [8]]
  console.log(numbers.flatMap(x => [x * 2]))  // [2, 4, 6, 8]

  // 等价于 map().flat(),但 flatMap 更高效
  console.log(numbers.map(x => [x * 2]).flat()) // [2, 4, 6, 8]

  // 处理多层嵌套
  console.log(numbers.flatMap(x => [[x * 2]])) // [[2], [4], [6], [8]]

  // 实际应用:字符串分词
  const sentences = ['Hello world', 'How are you', 'Fine thanks']
  const words = sentences.flatMap(sentence => sentence.split(' '))
  console.log(words) // ['Hello', 'world', 'How', 'are', 'you', 'Fine', 'thanks']

  // 处理异构数据
  const data = [
    { type: 'user', items: ['Alice', 'Bob'] },
    { type: 'admin', items: ['Charlie'] },
    { type: 'guest', items: [] }
  ]

  const allUsers = data.flatMap(group => group.items)
  console.log(allUsers) // ['Alice', 'Bob', 'Charlie']

  // 条件性展开
  const mixedData = [1, 2, 3, 4, 5]
  const result = mixedData.flatMap(x => 
    x % 2 === 0 ? [x, x * 2] : [x]  // 偶数展开为两个值
  )
  console.log(result) // [1, 2, 4, 3, 4, 8, 5]

  // 过滤并转换
  const items = ['apple', '', 'banana', null, 'cherry']
  const validItems = items.flatMap(item => 
    item && item.length > 0 ? [item.toUpperCase()] : []
  )
  console.log(validItems) // ['APPLE', 'BANANA', 'CHERRY']
}

Function.prototype.toString() 修订

toString() 方法现在返回精确的源代码,包括空格和注释。

{
  // 用户定义的函数
  function myFunction() {
    // 这是注释
    console.log('Hello World')
  }

  console.log(myFunction.toString())
  // function myFunction() {
  //   // 这是注释
  //   console.log('Hello World')
  // }

  // 箭头函数
  const arrowFn = (x, y) => x + y
  console.log(arrowFn.toString()) // (x, y) => x + 1

  // 内置函数
  console.log(parseInt.toString()) // function parseInt() { [native code] }
  console.log(Math.max.toString()) // function max() { [native code] }

  // 绑定函数
  const boundFn = myFunction.bind(null)
  console.log(boundFn.toString()) // function () { [native code] }

  // 异步函数
  async function asyncFn() {
    await Promise.resolve()
  }
  console.log(asyncFn.toString())
  // async function asyncFn() {
  //   await Promise.resolve()
  // }

  // 生成器函数
  function* generatorFn() {
    yield 1
    yield 2
  }
  console.log(generatorFn.toString())
  // function* generatorFn() {
  //   yield 1
  //   yield 2
  // }

  // 实际应用:代码分析和调试
  function analyzeFunction(fn) {
    const source = fn.toString()
    return {
      isAsync: source.includes('async'),
      isGenerator: source.includes('function*'),
      isArrow: source.includes('=>'),
      lineCount: source.split('\n').length,
      hasComments: source.includes('//') || source.includes('/*')
    }
  }

  console.log(analyzeFunction(myFunction))
  // { isAsync: false, isGenerator: false, isArrow: false, lineCount: 4, hasComments: true }
}

Well-formed JSON.stringify

修复 JSON.stringify() 对于某些 Unicode 字符的处理。

{
  // ES10 之前的问题:不正确处理代理对
  console.log(JSON.stringify('\uD83D\uDE0E')) // "😎" (正确)
  
  // 孤立的代理字符现在能正确处理
  console.log(JSON.stringify('\uD800'))       // "\\ud800"
  console.log(JSON.stringify('\uDFFF'))       // "\\udfff"

  // 混合的正常字符和代理字符
  const mixedString = 'Hello \uD83D\uDE0E World \uD800'
  console.log(JSON.stringify(mixedString))    // "Hello 😎 World \\ud800"

  // 实际应用:安全的数据序列化
  function safeStringify(obj) {
    try {
      return JSON.stringify(obj)
    } catch (error) {
      console.error('JSON.stringify failed:', error)
      return null
    }
  }

  // 处理包含各种 Unicode 字符的对象
  const unicodeData = {
    emoji: '🚀✨🎉',
    chinese: '你好世界',
    arabic: 'مرحبا بالعالم',
    surrogate: '\uD83D\uDE0E',
    invalid: '\uD800'
  }

  console.log(safeStringify(unicodeData))
  // {"emoji":"🚀✨🎉","chinese":"你好世界","arabic":"مرحبا بالعالم","surrogate":"😎","invalid":"\\ud800"}
}

Object.fromEntries()

将键值对列表转换为对象,是 Object.entries() 的逆操作。

{
  // 基本用法
  const entries = [
    ['name', 'Alice'],
    ['age', 25],
    ['city', 'New York']
  ]

  const person = Object.fromEntries(entries)
  console.log(person) // { name: 'Alice', age: 25, city: 'New York' }

  // 与 Object.entries() 的对称性
  const original = { a: 1, b: 2, c: 3 }
  const reconstructed = Object.fromEntries(Object.entries(original))
  console.log(reconstructed) // { a: 1, b: 2, c: 3 }

  // 从 Map 创建对象
  const map = new Map([
    ['foo', 'bar'],
    ['baz', 42]
  ])
  const objFromMap = Object.fromEntries(map)
  console.log(objFromMap) // { foo: 'bar', baz: 42 }

  // 数组转对象
  const indexedArray = [
    ['0', 'first'],
    ['1', 'second'],
    ['2', 'third']
  ]
  const objFromArray = Object.fromEntries(indexedArray)
  console.log(objFromArray) // { '0': 'first', '1': 'second', '2': 'third' }

  // 实际应用:数据转换和过滤
  const rawData = {
    name: 'John',
    age: 30,
    password: 'secret123',
    email: 'john@example.com',
    internal_id: 12345
  }

  // 过滤敏感数据
  const publicData = Object.fromEntries(
    Object.entries(rawData).filter(([key]) => 
      !key.startsWith('password') && !key.startsWith('internal_')
    )
  )
  console.log(publicData) // { name: 'John', age: 30, email: 'john@example.com' }

  // 转换数据格式
  const scores = {
    math: 85,
    science: 92,
    english: 78,
    history: 88
  }

  const gradeMap = {
    A: score => score >= 90,
    B: score => score >= 80,
    C: score => score >= 70,
    D: score => score >= 60,
    F: score => score < 60
  }

  const grades = Object.fromEntries(
    Object.entries(scores).map(([subject, score]) => {
      const grade = Object.keys(gradeMap).find(g => gradeMap[g](score))
      return [subject, grade]
    })
  )
  console.log(grades) // { math: 'B', science: 'A', english: 'C', history: 'B' }

  // URL 查询参数解析
  function parseQuery(queryString) {
    const params = new URLSearchParams(queryString)
    return Object.fromEntries(params)
  }

  const query = 'name=Alice&age=25&city=New%20York'
  console.log(parseQuery(query)) // { name: 'Alice', age: '25', city: 'New York' }
}

Symbol.prototype.description

提供 Symbol 的描述字符串,用于调试目的。

{
  // 创建带描述的 Symbol
  const sym1 = Symbol('debug info')
  const sym2 = Symbol('user-id')
  const sym3 = Symbol() // 无描述

  console.log(sym1.description) // 'debug info'
  console.log(sym2.description) // 'user-id'
  console.log(sym3.description) // undefined

  // Symbol 的唯一性不受描述影响
  console.log(Symbol('same') === Symbol('same')) // false
  console.log(sym1 === sym1) // true

  // 实际应用:调试和日志
  class EventEmitter {
    constructor() {
      this.events = new Map()
    }

    on(eventType, callback) {
      const symbol = Symbol(`event-${eventType}`)
      this.events.set(symbol, { type: eventType, callback })
      return symbol
    }

    off(symbol) {
      if (this.events.has(symbol)) {
        console.log(`Removing event: ${symbol.description}`)
        this.events.delete(symbol)
      }
    }

    listEvents() {
      const events = []
      for (const [symbol, data] of this.events) {
        events.push({
          symbol: symbol.description,
          type: data.type
        })
      }
      return events
    }
  }

  const emitter = new EventEmitter()
  const clickHandler = emitter.on('click', () => {})
  const keyHandler = emitter.on('keypress', () => {})

  console.log(emitter.listEvents())
  // [
  //   { symbol: 'event-click', type: 'click' },
  //   { symbol: 'event-keypress', type: 'keypress' }
  // ]

  // 私有属性标识
  const PRIVATE_PROPS = {
    userId: Symbol('private-user-id'),
    apiKey: Symbol('private-api-key'),
    internalState: Symbol('private-internal-state')
  }

  class User {
    constructor(id, name) {
      this[PRIVATE_PROPS.userId] = id
      this.name = name
    }

    getDebugInfo() {
      const props = Object.getOwnPropertySymbols(this)
      return props.map(prop => ({
        description: prop.description,
        value: typeof this[prop]
      }))
    }
  }

  const user = new User(123, 'Alice')
  console.log(user.getDebugInfo())
  // [{ description: 'private-user-id', value: 'number' }]
}

String.prototype.trimStart() / trimEnd()

分别移除字符串开头和结尾的空白字符。

{
  const str = '   Hello World!   '

  // 新方法
  console.log(str.trimStart()) // 'Hello World!   '
  console.log(str.trimEnd())   // '   Hello World!'
  console.log(str.trim())      // 'Hello World!'

  // 别名(向后兼容)
  console.log(str.trimLeft())  // 'Hello World!   ' (trimStart 的别名)
  console.log(str.trimRight()) // '   Hello World!' (trimEnd 的别名)

  // 验证别名关系
  console.log(String.prototype.trimLeft.name === 'trimStart')   // true
  console.log(String.prototype.trimRight.name === 'trimEnd')    // true

  // 处理不同类型的空白字符
  const whitespaceStr = '\t\n   Hello\t\n   '
  console.log(whitespaceStr.trimStart()) // 'Hello\t\n   '
  console.log(whitespaceStr.trimEnd())   // '\t\n   Hello'

  // 实际应用:代码格式化
  function formatCode(code) {
    return code
      .split('\n')
      .map(line => line.trimEnd()) // 移除行尾空白
      .join('\n')
      .trimStart() // 移除开头空行
  }

  const messyCode = `
    
    function hello() {
      console.log('Hello');    
    }    
  `

  console.log(formatCode(messyCode))
  // function hello() {
  //   console.log('Hello');
  // }

  // 解析用户输入
  function parseUserInput(input) {
    const lines = input.split('\n')
    return lines
      .map(line => line.trimEnd()) // 移除每行末尾空格
      .filter(line => line.trimStart() !== '') // 过滤空行
      .map(line => line.trimStart()) // 移除每行开头空格
  }

  const userInput = `
    First item    
       Second item
    
    Third item   
  `

  console.log(parseUserInput(userInput))
  // ['First item', 'Second item', 'Third item']

  // 处理配置文件
  function parseConfig(configText) {
    const config = {}
    const lines = configText.split('\n')

    for (const line of lines) {
      const trimmed = line.trim()
      if (trimmed && !trimmed.startsWith('#')) {
        const [key, ...valueParts] = trimmed.split('=')
        if (key && valueParts.length > 0) {
          config[key.trimEnd()] = valueParts.join('=').trimStart()
        }
      }
    }

    return config
  }

  const configFile = `
    # Database configuration
    host = localhost    
    port = 5432
    database =   myapp   
    # user = admin
    timeout = 30
  `

  console.log(parseConfig(configFile))
  // { host: 'localhost', port: '5432', database: 'myapp', timeout: '30' }
}

ECMAScript 2018(ES9)

RegExp Features

dotAll flag (s)

s 标志使 . 能够匹配任何字符,包括换行符。

{
  // 传统情况下,. 不能匹配换行符
  console.log(/foo.bar/.test('fooabar'))  // true
  console.log(/foo.bar/.test('foo\nbar')) // false

  // 使用 dotAll 标志
  console.log(/foo.bar/s.test('foo\nbar')) // true

  // 检查标志
  const regex = /foo.bar/gisu
  console.log(regex.dotAll) // true
  console.log(regex.flags)  // 'gisu'

  // 实际应用:多行文本解析
  const htmlContent = `
    <div class="header">
      Title
    </div>
  `

  // 匹配跨行的 HTML 标签
  const tagRegex = /<div.*?>.*?<\/div>/s
  console.log(tagRegex.test(htmlContent)) // true

  // 解析配置文件中的多行值
  const config = `
    name = MyApp
    description = This is a long
    description that spans
    multiple lines
    version = 1.0.0
  `

  const multiLineValue = /description\s*=\s*(.*?)(?=\n\w+\s*=|\n*$)/s
  const match = config.match(multiLineValue)
  if (match) {
    console.log(match[1].trim().replace(/\s+/g, ' '))
    // 'This is a long description that spans multiple lines'
  }
}

Named Capture Groups

使用命名捕获组让正则表达式更可读和可维护。

{
  // 传统的数字索引方式
  const dateMatch = '2020-03-28'.match(/(\d{4})-(\d{2})-(\d{2})/)
  console.log(dateMatch[1]) // '2020'
  console.log(dateMatch[2]) // '03'
  console.log(dateMatch[3]) // '28'

  // 命名捕获组
  const namedMatch = '2020-03-28'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
  console.log(namedMatch.groups.year)  // '2020'
  console.log(namedMatch.groups.month) // '03'
  console.log(namedMatch.groups.day)   // '28'

  // 实际应用:解析电子邮件
  const email = 'john.doe@example.com'
  const emailRegex = /(?<username>[^@]+)@(?<domain>[^.]+)\.(?<tld>.+)/
  const emailMatch = email.match(emailRegex)
  
  if (emailMatch) {
    const { username, domain, tld } = emailMatch.groups
    console.log({ username, domain, tld })
    // { username: 'john.doe', domain: 'example', tld: 'com' }
  }

  // 解析 URL
  const url = 'https://api.github.com/users/octocat'
  const urlRegex = /(?<protocol>https?):\/\/(?<host>[^\/]+)(?<path>\/.*)?/
  const urlMatch = url.match(urlRegex)
  
  if (urlMatch) {
    console.log(urlMatch.groups)
    // { protocol: 'https', host: 'api.github.com', path: '/users/octocat' }
  }

  // 在替换中使用命名组
  const text = 'Call me at 555-123-4567'
  const phoneRegex = /(?<area>\d{3})-(?<exchange>\d{3})-(?<number>\d{4})/
  const formatted = text.replace(phoneRegex, '($<area>) $<exchange>-$<number>')
  console.log(formatted) // 'Call me at (555) 123-4567'

  // 解构赋值与命名组
  function parseLogEntry(logLine) {
    const logRegex = /(?<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(?<level>\w+)\] (?<message>.*)/
    const match = logLine.match(logRegex)
    
    if (match) {
      return match.groups
    }
    return null
  }

  const log = '2023-12-25 10:30:45 [ERROR] Database connection failed'
  console.log(parseLogEntry(log))
  // { timestamp: '2023-12-25 10:30:45', level: 'ERROR', message: 'Database connection failed' }
}

Lookbehind Assertions

正向和负向回顾断言,检查某个位置之前的内容。

{
  const text = 'hello world'

  // 正向前瞻 (?=) - 匹配后面跟着指定内容的位置
  console.log(text.match(/hello(?=\sworld)/)) // ['hello']

  // 正向回顾 (?<=) - 匹配前面是指定内容的位置
  console.log(text.match(/(?<=hello\s)world/)) // ['world']

  // 负向回顾 (?<!) - 匹配前面不是指定内容的位置
  console.log(text.match(/(?<!helle\s)world/)) // ['world']

  // 实际应用:价格解析
  const prices = '$100 €200 ¥300 100USD'
  
  // 匹配前面有货币符号的数字
  const withSymbol = prices.match(/(?<=[$€¥])\d+/g)
  console.log(withSymbol) // ['100', '200', '300']

  // 匹配后面有货币代码的数字
  const withCode = prices.match(/\d+(?=[A-Z]{3})/g)
  console.log(withCode) // ['100']

  // 匹配不在引号内的单词
  const code = 'const message = "hello world"; const name = "Alice"'
  const outsideQuotes = code.match(/(?<!")(?<!\w)\w+(?=\s*=)/g)
  console.log(outsideQuotes) // ['const', 'const']

  // 密码验证:至少8位,包含大小写字母和数字
  function validatePassword(password) {
    const hasLower = /(?=.*[a-z])/.test(password)
    const hasUpper = /(?=.*[A-Z])/.test(password)
    const hasDigit = /(?=.*\d)/.test(password)
    const isLongEnough = password.length >= 8

    return hasLower && hasUpper && hasDigit && isLongEnough
  }

  console.log(validatePassword('Password123')) // true
  console.log(validatePassword('password'))    // false

  // 提取 HTML 标签中的属性值
  const html = '<img src="image.jpg" alt="A beautiful sunset" width="300">'
  const attributeRegex = /(?<=\w+=")[^"]+(?=")/g
  const attributes = html.match(attributeRegex)
  console.log(attributes) // ['image.jpg', 'A beautiful sunset', '300']
}

Asynchronous Iteration

for-await-of

异步迭代器,用于处理异步可迭代对象。

{
  // 创建异步生成器
  async function* asyncGenerator() {
    yield await Promise.resolve(1)
    yield await Promise.resolve(2)
    yield await Promise.resolve(3)
  }

  // 使用 for-await-of 循环
  async function consumeAsyncGenerator() {
    for await (const value of asyncGenerator()) {
      console.log(value) // 1, 2, 3 (按顺序输出)
    }
  }

  // 实际应用:处理异步数据流
  function createAsyncIterable(urls) {
    return {
      [Symbol.asyncIterator]() {
        let index = 0
        return {
          async next() {
            if (index < urls.length) {
              try {
                const response = await fetch(urls[index++])
                const data = await response.json()
                return { value: data, done: false }
              } catch (error) {
                return { value: error, done: false }
              }
            } else {
              return { done: true }
            }
          }
        }
      }
    }
  }

  // 使用示例
  async function fetchMultipleAPIs() {
    const urls = ['/api/users', '/api/posts', '/api/comments']
    const asyncIterable = createAsyncIterable(urls)

    for await (const result of asyncIterable) {
      if (result instanceof Error) {
        console.error('Failed to fetch:', result.message)
      } else {
        console.log('Fetched data:', result)
      }
    }
  }

  // 自定义异步迭代器示例
  const timeoutIterable = {
    [Symbol.asyncIterator]() {
      let count = 0
      return {
        async next() {
          if (count < 3) {
            await new Promise(resolve => setTimeout(resolve, 1000))
            return { value: `Item ${++count}`, done: false }
          }
          return { done: true }
        }
      }
    }
  }

  async function processTimeouts() {
    console.log('Starting async iteration...')
    for await (const item of timeoutIterable) {
      console.log(item) // 每秒输出一次
    }
    console.log('Completed!')
  }

  // 处理 ReadableStream
  async function processStream(stream) {
    const reader = stream.getReader()
    try {
      for await (const chunk of readableStreamAsyncIterable(reader)) {
        console.log('Chunk:', chunk)
      }
    } finally {
      reader.releaseLock()
    }
  }

  async function* readableStreamAsyncIterable(reader) {
    try {
      while (true) {
        const { done, value } = await reader.read()
        if (done) return
        yield value
      }
    } finally {
      reader.releaseLock()
    }
  }
}

Object Rest/Spread Properties

对象的剩余/扩展属性,类似于数组的扩展语法。

{
  // 对象扩展
  const obj1 = { a: 1, b: 2 }
  const obj2 = { c: 3, d: 4 }
  
  const combined = { ...obj1, ...obj2, e: 5 }
  console.log(combined) // { a: 1, b: 2, c: 3, d: 4, e: 5 }

  // 对象剩余属性
  const person = {
    name: 'Alice',
    age: 30,
    city: 'New York',
    country: 'USA',
    occupation: 'Developer'
  }

  const { name, age, ...rest } = person
  console.log(name) // 'Alice'
  console.log(age)  // 30
  console.log(rest) // { city: 'New York', country: 'USA', occupation: 'Developer' }

  // 实际应用:配置对象合并
  const defaultConfig = {
    theme: 'light',
    language: 'en',
    notifications: true,
    autoSave: false
  }

  const userConfig = {
    theme: 'dark',
    notifications: false
  }

  const finalConfig = { ...defaultConfig, ...userConfig }
  console.log(finalConfig)
  // { theme: 'dark', language: 'en', notifications: false, autoSave: false }

  // 函数参数解构
  function updateUser({ id, ...updates }) {
    console.log(`Updating user ${id} with:`, updates)
    // 模拟数据库更新
    return { id, ...updates, updatedAt: new Date() }
  }

  const user = updateUser({
    id: 123,
    name: 'Bob',
    email: 'bob@example.com',
    age: 25
  })
  console.log(user)

  // 过滤对象属性
  function omit(obj, keys) {
    const { [keys]: _, ...rest } = obj
    return rest
  }

  function omitMultiple(obj, keys) {
    const result = { ...obj }
    keys.forEach(key => delete result[key])
    return result
  }

  const data = { a: 1, b: 2, c: 3, d: 4 }
  console.log(omitMultiple(data, ['b', 'd'])) // { a: 1, c: 3 }

  // 条件性属性添加
  function createUser(name, email, isAdmin = false) {
    return {
      name,
      email,
      createdAt: new Date(),
      ...(isAdmin && { role: 'admin', permissions: ['read', 'write', 'delete'] })
    }
  }

  console.log(createUser('Alice', 'alice@example.com'))
  // { name: 'Alice', email: 'alice@example.com', createdAt: Date }

  console.log(createUser('Bob', 'bob@example.com', true))
  // { name: 'Bob', email: 'bob@example.com', createdAt: Date, role: 'admin', permissions: [...] }

  // 深度克隆(浅层)
  function shallowClone(obj) {
    return { ...obj }
  }

  // 对象数组的去重
  function uniqueBy(array, key) {
    const seen = new Set()
    return array.filter(item => {
      const value = item[key]
      if (seen.has(value)) {
        return false
      }
      seen.add(value)
      return true
    })
  }

  const users = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    { id: 1, name: 'Alice Duplicate' }
  ]

  console.log(uniqueBy(users, 'id'))
  // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
}

Promise.prototype.finally()

无论 Promise 成功或失败都会执行的回调。

{
  // 基本用法
  function fetchData() {
    return fetch('/api/data')
      .then(response => response.json())
      .then(data => {
        console.log('Data received:', data)
        return data
      })
      .catch(error => {
        console.error('Error:', error)
        throw error
      })
      .finally(() => {
        console.log('Request completed') // 无论成功失败都会执行
      })
  }

  // 实际应用:加载状态管理
  class DataLoader {
    constructor() {
      this.isLoading = false
      this.data = null
      this.error = null
    }

    async loadData(url) {
      this.isLoading = true
      this.error = null

      try {
        const response = await fetch(url)
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`)
        }
        this.data = await response.json()
        return this.data
      } catch (error) {
        this.error = error
        throw error
      } finally {
        this.isLoading = false // 无论成功失败都重置加载状态
      }
    }
  }

  // 资源清理示例
  async function processFile(filename) {
    const file = await openFile(filename)
    
    try {
      const data = await parseFile(file)
      return await processData(data)
    } catch (error) {
      console.error('Processing failed:', error)
      throw error
    } finally {
      await closeFile(file) // 确保文件始终被关闭
    }
  }

  // 模拟网络请求的工具函数
  function simulateRequest(shouldSucceed = true, delay = 1000) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (shouldSucceed) {
          resolve({ data: 'Success', timestamp: Date.now() })
        } else {
          reject(new Error('Request failed'))
        }
      }, delay)
    })
  }

  // 使用 finally 进行日志记录
  async function requestWithLogging(url) {
    const startTime = Date.now()
    
    try {
      const result = await simulateRequest(Math.random() > 0.5)
      console.log('Request succeeded:', result)
      return result
    } catch (error) {
      console.error('Request failed:', error.message)
      throw error
    } finally {
      const duration = Date.now() - startTime
      console.log(`Request to ${url} completed in ${duration}ms`)
    }
  }

  // 批量请求处理
  async function batchProcess(tasks) {
    const results = []
    let completed = 0
    
    for (const task of tasks) {
      try {
        const result = await task()
        results.push({ success: true, data: result })
      } catch (error) {
        results.push({ success: false, error: error.message })
      } finally {
        completed++
        console.log(`Progress: ${completed}/${tasks.length}`)
      }
    }
    
    return results
  }

  // 示例使用
  // requestWithLogging('/api/users')
}

ECMAScript 2017(ES8)

Object.values() / Object.entries()

与 Object.keys() 配套的方法,分别返回对象的值数组和键值对数组。

{
  const obj = { a: 1, b: 2, c: 3 }

  // Object.keys() - 返回键数组
  console.log(Object.keys(obj)) // ['a', 'b', 'c']

  // Object.values() - 返回值数组
  console.log(Object.values(obj)) // [1, 2, 3]

  // Object.entries() - 返回键值对数组
  console.log(Object.entries(obj)) // [['a', 1], ['b', 2], ['c', 3]]

  // 传统遍历方式
  Object.keys(obj).forEach((key, index) => {
    console.log(key, obj[key])
  })

  // 使用 Object.values() 遍历值
  Object.values(obj).forEach(value => {
    console.log(value) // 1, 2, 3
  })

  // 使用 Object.entries() 遍历键值对
  Object.entries(obj).forEach(([key, value]) => {
    console.log(`${key} is ${value}`)
  })
  // a is 1, b is 2, c is 3

  // 结合 for...of 使用
  for (const value of Object.values(obj)) {
    console.log(value)
  }

  for (const [key, value] of Object.entries(obj)) {
    console.log(`${key}: ${value}`)
  }

  // 实际应用:数据过滤和转换
  const grades = {
    alice: 96,
    bob: 84,
    charlie: 92,
    diana: 88
  }

  // 获取所有分数
  const scores = Object.values(grades)
  console.log(scores) // [96, 84, 92, 88]

  // 过滤高分学生
  const highScorers = Object.entries(grades)
    .filter(([name, score]) => score > 90)
    .map(([name, score]) => name)
  console.log(highScorers) // ['alice', 'charlie']

  // 计算平均分
  const average = Object.values(grades).reduce((sum, score) => sum + score, 0) / Object.values(grades).length
  console.log(average) // 90

  // 对象转换
  const cityTemperatures = {
    'New York': '78/50',
    'London': '64/45',
    'Tokyo': '82/55'
  }

  const temperatureData = Object.entries(cityTemperatures).map(([city, temp]) => {
    const [high, low] = temp.split('/').map(Number)
    return { city, high, low, average: (high + low) / 2 }
  })
  console.log(temperatureData)
  // [
  //   { city: 'New York', high: 78, low: 50, average: 64 },
  //   { city: 'London', high: 64, low: 45, average: 54.5 },
  //   { city: 'Tokyo', high: 82, low: 55, average: 68.5 }
  // ]
}

String.prototype.padStart() / padEnd()

字符串填充方法,在字符串的开头或结尾填充指定字符到目标长度。

{
  // padStart() - 在开头填充
  console.log('5'.padStart(3, '0'))        // '005'
  console.log('react'.padStart(10))        // '     react'
  console.log('react'.padStart(10, '_'))   // '_____react'

  // padEnd() - 在结尾填充
  console.log('5'.padEnd(3, '0'))          // '500'
  console.log('react'.padEnd(10))          // 'react     '
  console.log('react'.padEnd(10, '_'))     // 'react_____'

  // 实际应用:数字格式化
  for (let i = 1; i <= 12; i++) {
    const month = i.toString().padStart(2, '0')
    console.log(`2023-${month}-01`) // 2023-01-01, 2023-02-01, ...
  }

  // 财务报表对齐
  const amounts = ['0.00', '1,250.50', '15,000.00', '150.75']
  amounts.forEach(amount => {
    console.log(amount.padStart(12))
  })
  // 输出:
  //        0.00
  //    1,250.50
  //   15,000.00
  //      150.75

  // 创建简单的表格
  const data = [
    ['Name', 'Age', 'City'],
    ['Alice', '25', 'New York'],
    ['Bob', '30', 'London'],
    ['Charlie', '35', 'Tokyo']
  ]

  data.forEach(row => {
    const formatted = row.map((cell, index) => {
      const width = index === 0 ? 10 : index === 1 ? 5 : 12
      return cell.padEnd(width)
    }).join('|')
    console.log(formatted)
  })

  // 进度条显示
  function showProgress(current, total, width = 20) {
    const percentage = Math.floor((current / total) * 100)
    const filled = Math.floor((current / total) * width)
    const bar = '█'.repeat(filled).padEnd(width, '░')
    return `[${bar}] ${percentage.toString().padStart(3)}%`
  }

  console.log(showProgress(7, 10))  // [██████████████░░░░░░]  70%
  console.log(showProgress(3, 10))  // [██████░░░░░░░░░░░░░░]  30%

  // 日志格式化
  function formatLog(level, message) {
    const timestamp = new Date().toISOString()
    const formattedLevel = level.padEnd(5)
    return `${timestamp} [${formattedLevel}] ${message}`
  }

  console.log(formatLog('INFO', 'Application started'))
  console.log(formatLog('ERROR', 'Connection failed'))
  // 2023-12-25T10:30:00.000Z [INFO ] Application started
  // 2023-12-25T10:30:01.000Z [ERROR] Connection failed
}

Object.getOwnPropertyDescriptors()

返回对象所有自有属性的属性描述符,用于深度克隆和对象合并。

{
  // 基本用法
  const obj = {
    name: 'Alice',
    age: 25,
    get info() {
      return `${this.name} is ${this.age} years old`
    },
    set info(value) {
      const [name, age] = value.split(' is ')
      this.name = name
      this.age = parseInt(age)
    }
  }

  const descriptors = Object.getOwnPropertyDescriptors(obj)
  console.log(descriptors)
  /*
  {
    name: { value: 'Alice', writable: true, enumerable: true, configurable: true },
    age: { value: 25, writable: true, enumerable: true, configurable: true },
    info: {
      get: [Function: get info],
      set: [Function: set info],
      enumerable: true,
      configurable: true
    }
  }
  */

  // 深度克隆(包含 getter/setter)
  function deepClone(obj) {
    return Object.create(
      Object.getPrototypeOf(obj),
      Object.getOwnPropertyDescriptors(obj)
    )
  }

  const cloned = deepClone(obj)
  console.log(cloned.info) // 'Alice is 25 years old'
  cloned.name = 'Bob'
  console.log(obj.name)    // 'Alice' (原对象未改变)
  console.log(cloned.info) // 'Bob is 25 years old'

  // 对象合并(保留属性描述符)
  function mergeObjects(target, source) {
    Object.defineProperties(target, Object.getOwnPropertyDescriptors(source))
    return target
  }

  const target = { x: 1 }
  const source = {
    y: 2,
    get z() { return this.y * 2 }
  }

  const merged = mergeObjects(target, source)
  console.log(merged.z) // 4

  // 实际应用:配置对象处理
  const defaultConfig = {
    host: 'localhost',
    port: 3000,
    get url() {
      return `http://${this.host}:${this.port}`
    }
  }

  // 隐藏某些属性
  Object.defineProperty(defaultConfig, 'secret', {
    value: 'hidden-key',
    writable: false,
    enumerable: false,
    configurable: false
  })

  console.log(Object.keys(defaultConfig))                    // ['host', 'port']
  console.log(Object.getOwnPropertyDescriptors(defaultConfig))
  // 显示所有属性描述符,包括不可枚举的

  // 创建不可变对象
  function createImmutable(obj) {
    const descriptors = Object.getOwnPropertyDescriptors(obj)
    
    // 将所有属性设为不可写
    Object.keys(descriptors).forEach(key => {
      if (descriptors[key].value !== undefined) {
        descriptors[key].writable = false
      }
      descriptors[key].configurable = false
    })

    return Object.create(Object.getPrototypeOf(obj), descriptors)
  }

  const immutableObj = createImmutable({ a: 1, b: 2 })
  // immutableObj.a = 999 // 在严格模式下会报错
  console.log(immutableObj.a) // 1
}

Trailing Commas in Function Parameters

函数参数列表和调用中允许尾随逗号。

{
  // ES8 之前 - 语法错误
  // function oldStyle(a, b, c,) { } // SyntaxError

  // ES8 - 允许尾随逗号
  function newStyle(
    param1,
    param2,
    param3, // 尾随逗号 OK
  ) {
    return param1 + param2 + param3
  }

  // 函数调用中也可以使用
  const result = newStyle(
    1,
    2,
    3, // 尾随逗号 OK
  )

  console.log(result) // 6

  // 实际应用:多行参数定义
  function createUser(
    firstName,
    lastName,
    email,
    age,
    address,
    phoneNumber,
    isActive, // 方便添加新参数
  ) {
    return {
      firstName,
      lastName,
      email,
      age,
      address,
      phoneNumber,
      isActive
    }
  }

  // API 调用示例
  async function fetchUserData(
    userId,
    includeProfile,
    includeSettings,
    includeHistory, // 便于扩展
  ) {
    const params = new URLSearchParams({
      userId,
      includeProfile,
      includeSettings,
      includeHistory,
    })
    
    return fetch(`/api/users?${params}`)
  }

  // 数组和对象中早已支持
  const array = [
    1,
    2,
    3, // OK since ES3
  ]

  const object = {
    a: 1,
    b: 2,
    c: 3, // OK since ES5
  }

  // 有助于版本控制
  // 添加新参数时,git diff 更清晰
  function oldWay(a, b, c) {
    // 添加参数 d 时,需要修改 c 行
  }

  function newWay(
    a,
    b,
    c,
    // 添加 d 只需要新增一行,不影响其他行
  ) {
    // 更好的版本控制体验
  }
}

Async/Await

基于 Promise 的异步编程语法糖,让异步代码看起来像同步代码。

{
  // Promise 方式
  function fetchUserWithPromise(userId) {
    return fetch(`/api/users/${userId}`)
      .then(response => response.json())
      .then(user => {
        return fetch(`/api/users/${user.id}/posts`)
      })
      .then(response => response.json())
      .then(posts => {
        return { user, posts }
      })
      .catch(error => {
        console.error('Error:', error)
        throw error
      })
  }

  // Async/Await 方式
  async function fetchUserWithAsync(userId) {
    try {
      const userResponse = await fetch(`/api/users/${userId}`)
      const user = await userResponse.json()
      
      const postsResponse = await fetch(`/api/users/${user.id}/posts`)
      const posts = await postsResponse.json()
      
      return { user, posts }
    } catch (error) {
      console.error('Error:', error)
      throw error
    }
  }

  // await 的执行机制
  async function demonstrateAwait() {
    console.log('1. Start')
    
    // await 非 Promise 值
    const value1 = await 42
    console.log('2. Non-promise value:', value1) // 42
    
    // await Promise
    const value2 = await Promise.resolve('Hello')
    console.log('3. Promise value:', value2) // 'Hello'
    
    // await 延迟的 Promise
    const value3 = await new Promise(resolve => {
      setTimeout(() => resolve('Delayed'), 1000)
    })
    console.log('4. Delayed value:', value3) // 'Delayed' (1秒后)
    
    return 'Done'
  }

  // demonstrateAwait().then(result => console.log('5. Final:', result))

  // 错误处理
  async function handleErrors() {
    try {
      const result = await Promise.reject(new Error('Something went wrong'))
    } catch (error) {
      console.log('Caught error:', error.message)
    }

    // 多个 await 的错误处理
    try {
      const user = await fetchUser()
      const posts = await fetchPosts(user.id)
      const comments = await fetchComments(posts)
      return { user, posts, comments }
    } catch (error) {
      // 任何一个 await 失败都会被捕获
      console.error('Pipeline failed:', error)
      return null
    }
  }

  // 并发执行
  async function parallelExecution() {
    // 串行执行 - 较慢
    const start1 = Date.now()
    const result1 = await delay(1000, 'First')
    const result2 = await delay(1000, 'Second')
    console.log(`Serial: ${Date.now() - start1}ms`) // ~2000ms

    // 并行执行 - 较快
    const start2 = Date.now()
    const [result3, result4] = await Promise.all([
      delay(1000, 'Third'),
      delay(1000, 'Fourth')
    ])
    console.log(`Parallel: ${Date.now() - start2}ms`) // ~1000ms
  }

  function delay(ms, value) {
    return new Promise(resolve => setTimeout(() => resolve(value), ms))
  }

  // 实际应用:数据处理管道
  async function processUserData(userId) {
    try {
      // 获取用户基本信息
      const user = await fetchUser(userId)
      console.log(`Processing user: ${user.name}`)

      // 并行获取相关数据
      const [profile, settings, activity] = await Promise.all([
        fetchUserProfile(userId),
        fetchUserSettings(userId),
        fetchUserActivity(userId)
      ])

      // 处理数据
      const processedData = {
        ...user,
        profile: await processProfile(profile),
        settings: normalizeSettings(settings),
        activitySummary: summarizeActivity(activity)
      }

      // 保存结果
      await saveProcessedData(processedData)
      
      return processedData
    } catch (error) {
      console.error(`Failed to process user ${userId}:`, error)
      throw error
    }
  }

  // 模拟异步函数
  async function fetchUser(id) {
    await delay(100)
    return { id, name: `User ${id}`, email: `user${id}@example.com` }
  }

  async function fetchUserProfile(id) {
    await delay(150)
    return { userId: id, bio: 'A great user', avatar: 'avatar.jpg' }
  }

  async function fetchUserSettings(id) {
    await delay(80)
    return { theme: 'dark', notifications: true, language: 'en' }
  }

  async function fetchUserActivity(id) {
    await delay(200)
    return [
      { action: 'login', timestamp: Date.now() },
      { action: 'view_page', timestamp: Date.now() - 1000 }
    ]
  }

  async function processProfile(profile) {
    await delay(50)
    return { ...profile, processed: true }
  }

  function normalizeSettings(settings) {
    return { ...settings, normalized: true }
  }

  function summarizeActivity(activity) {
    return { totalActions: activity.length, lastAction: activity[0] }
  }

  async function saveProcessedData(data) {
    await delay(100)
    console.log('Data saved successfully')
  }

  // 使用示例
  // processUserData(123).then(data => console.log('Final result:', data))
}

ECMAScript 2016(ES7)

Exponentiation Operator (**)

指数运算符,用于计算数字的幂运算,是 Math.pow() 的语法糖。

{
  // 传统方法:自定义函数
  function pow(x, y) {
    let result = 1
    for (let i = 0; i < y; i++) {
      result *= x
    }
    return result
  }
  console.log(pow(2, 5)) // 32

  // 传统方法:Math.pow()
  console.log(Math.pow(2, 5)) // 32
  console.log(Math.pow(3, 3)) // 27
  console.log(Math.pow(10, -2)) // 0.01

  // ES7 新语法:指数运算符
  console.log(2 ** 5)    // 32
  console.log(3 ** 3)    // 27
  console.log(10 ** -2)  // 0.01

  // 运算符优先级
  console.log(2 ** 3 ** 2)   // 512 (等同于 2 ** (3 ** 2))
  console.log((2 ** 3) ** 2) // 64

  // 与其他运算符结合
  console.log(2 ** 3 * 4)    // 32 (等同于 (2 ** 3) * 4)
  console.log(2 * 3 ** 2)    // 18 (等同于 2 * (3 ** 2))

  // 赋值运算符形式
  let num = 2
  num **= 3
  console.log(num) // 8

  // 实际应用:数学计算
  function calculateCompoundInterest(principal, rate, time) {
    return principal * (1 + rate) ** time
  }

  const investment = calculateCompoundInterest(1000, 0.05, 10)
  console.log(investment.toFixed(2)) // 1628.89

  // 计算圆的面积
  function circleArea(radius) {
    return Math.PI * radius ** 2
  }
  console.log(circleArea(5).toFixed(2)) // 78.54

  // 计算立方体体积
  function cubeVolume(side) {
    return side ** 3
  }
  console.log(cubeVolume(4)) // 64

  // 科学计算:计算距离
  function distance3D(x1, y1, z1, x2, y2, z2) {
    return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2)
  }
  console.log(distance3D(0, 0, 0, 3, 4, 5).toFixed(2)) // 7.07

  // 二进制运算
  function powerOfTwo(n) {
    return 2 ** n
  }
  console.log([0, 1, 2, 3, 4, 5].map(powerOfTwo)) // [1, 2, 4, 8, 16, 32]

  // 处理大数
  console.log(10 ** 6)   // 1000000
  console.log(2 ** 53)   // 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)
  console.log(2 ** 10)   // 1024 (1KB)
  console.log(2 ** 20)   // 1048576 (1MB)

  // 分数指数(开方)
  console.log(9 ** 0.5)   // 3 (平方根)
  console.log(8 ** (1/3)) // 2 (立方根)
  console.log(16 ** 0.25) // 2 (四次方根)

  // 与 BigInt 结合使用
  console.log(2n ** 100n) // 1267650600228229401496703205376n

  // 性能比较
  function performanceTest() {
    const start1 = performance.now()
    for (let i = 0; i < 1000000; i++) {
      Math.pow(2, 10)
    }
    const end1 = performance.now()

    const start2 = performance.now()
    for (let i = 0; i < 1000000; i++) {
      2 ** 10
    }
    const end2 = performance.now()

    console.log(`Math.pow(): ${end1 - start1}ms`)
    console.log(`** operator: ${end2 - start2}ms`)
  }
  // performanceTest() // ** 运算符通常更快
}

Array.prototype.includes()

判断数组是否包含指定值,返回布尔值,比 indexOf() 更语义化。

{
  const numbers = [1, 2, 3, 4, 5, NaN]
  const fruits = ['apple', 'banana', 'orange']

  // 基本用法
  console.log(numbers.includes(3))     // true
  console.log(numbers.includes(6))     // false
  console.log(fruits.includes('apple')) // true

  // 与 indexOf() 的比较
  console.log(numbers.indexOf(3) !== -1)  // true (传统方法)
  console.log(numbers.includes(3))        // true (ES7 方法,更直观)

  // 处理 NaN
  console.log(numbers.indexOf(NaN))    // -1 (indexOf 无法正确处理 NaN)
  console.log(numbers.includes(NaN))   // true (includes 可以正确处理 NaN)

  // 使用 fromIndex 参数
  const array = [1, 2, 3, 4, 5, 3, 2, 1]
  
  console.log(array.includes(3))      // true
  console.log(array.includes(3, 3))   // true (从索引 3 开始查找)
  console.log(array.includes(3, 6))   // false (从索引 6 开始查找)

  // 负数索引
  console.log(array.includes(2, -3))  // true (从倒数第3个位置开始)
  console.log(array.includes(1, -1))  // true (从最后一个位置开始)

  // 实际应用:条件判断
  const userRoles = ['admin', 'editor', 'viewer']
  const currentUser = { role: 'editor' }

  if (userRoles.includes(currentUser.role)) {
    console.log('User has valid role')
  }

  // 表单验证
  function validateEmail(email) {
    const validDomains = ['gmail.com', 'yahoo.com', 'hotmail.com']
    const domain = email.split('@')[1]
    return validDomains.includes(domain)
  }

  console.log(validateEmail('user@gmail.com'))    // true
  console.log(validateEmail('user@unknown.com'))  // false

  // 过滤数组
  const allUsers = [
    { name: 'Alice', status: 'active' },
    { name: 'Bob', status: 'inactive' },
    { name: 'Charlie', status: 'pending' },
    { name: 'Diana', status: 'active' }
  ]

  const activeStatuses = ['active', 'pending']
  const filteredUsers = allUsers.filter(user => 
    activeStatuses.includes(user.status)
  )
  console.log(filteredUsers) 
  // [{ name: 'Alice', status: 'active' }, { name: 'Charlie', status: 'pending' }, { name: 'Diana', status: 'active' }]

  // 权限检查
  class PermissionManager {
    constructor(userPermissions) {
      this.permissions = userPermissions
    }

    hasPermission(permission) {
      return this.permissions.includes(permission)
    }

    hasAnyPermission(permissions) {
      return permissions.some(permission => this.permissions.includes(permission))
    }

    hasAllPermissions(permissions) {
      return permissions.every(permission => this.permissions.includes(permission))
    }
  }

  const pm = new PermissionManager(['read', 'write', 'delete'])
  
  console.log(pm.hasPermission('read'))              // true
  console.log(pm.hasPermission('admin'))             // false
  console.log(pm.hasAnyPermission(['admin', 'read'])) // true
  console.log(pm.hasAllPermissions(['read', 'write'])) // true

  // 黑名单检查
  function isValidUrl(url) {
    const blockedDomains = ['spam.com', 'malware.net', 'phishing.org']
    const domain = new URL(url).hostname
    return !blockedDomains.includes(domain)
  }

  console.log(isValidUrl('https://google.com'))    // true
  console.log(isValidUrl('https://spam.com'))      // false

  // 菜单项激活状态
  function getActiveMenuItems(currentPath, menuItems) {
    return menuItems.map(item => ({
      ...item,
      active: item.paths.includes(currentPath)
    }))
  }

  const menuItems = [
    { name: 'Home', paths: ['/', '/home'] },
    { name: 'About', paths: ['/about', '/about-us'] },
    { name: 'Contact', paths: ['/contact', '/contact-us'] }
  ]

  const activeItems = getActiveMenuItems('/about', menuItems)
  console.log(activeItems)

  // 搜索功能
  function searchUsers(query, users) {
    const keywords = query.toLowerCase().split(' ')
    return users.filter(user => 
      keywords.every(keyword => 
        user.name.toLowerCase().includes(keyword) ||
        user.tags.some(tag => tag.toLowerCase().includes(keyword))
      )
    )
  }

  const users = [
    { name: 'John Doe', tags: ['developer', 'javascript'] },
    { name: 'Jane Smith', tags: ['designer', 'ui/ux'] },
    { name: 'Bob Johnson', tags: ['developer', 'python'] }
  ]

  const searchResults = searchUsers('john developer', users)
  console.log(searchResults) // [{ name: 'John Doe', tags: ['developer', 'javascript'] }]

  // 限制:只能检测简单类型
  const complexArray = [
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
    [1, 2, 3]
  ]

  console.log(complexArray.includes({ id: 1, name: 'Alice' })) // false (对象引用不同)
  console.log(complexArray.includes([1, 2, 3]))               // false (数组引用不同)

  // 解决复杂类型的检查
  function includesObject(array, target, key) {
    return array.some(item => item[key] === target[key])
  }

  console.log(includesObject(complexArray, { id: 1, name: 'Alice' }, 'id')) // true

  function includesArray(array, target) {
    return array.some(item => 
      Array.isArray(item) && 
      Array.isArray(target) && 
      item.length === target.length &&
      item.every((val, index) => val === target[index])
    )
  }

  console.log(includesArray(complexArray, [1, 2, 3])) // true
}

ECMAScript 2015(ES6)

let 和 const

新的变量声明方式,具有块级作用域和更严格的约束。

{
  // let 特性
  // 1. 块级作用域
  for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100) // 0, 1, 2
  }

  for (var j = 0; j < 3; j++) {
    setTimeout(() => console.log(j), 100) // 3, 3, 3
  }

  // 2. 不存在变量提升
  // console.log(a) // ReferenceError: Cannot access 'a' before initialization
  let a = 1

  // 3. 暂时性死区
  let x = 1
  {
    // console.log(x) // ReferenceError: Cannot access 'x' before initialization
    let x = 2
  }

  // 4. 不允许重复声明
  // let name = 'Alice'
  // let name = 'Bob' // SyntaxError: Identifier 'name' has already been declared

  // const 特性
  // 1. 声明时必须赋值
  const PI = 3.14159

  // 2. 不能重新赋值
  // PI = 3.14 // TypeError: Assignment to constant variable

  // 3. 对象内容可变
  const user = { name: 'Alice' }
  user.name = 'Bob' // 允许
  user.age = 25     // 允许
  console.log(user) // { name: 'Bob', age: 25 }

  // 冻结对象使其完全不可变
  const frozenUser = Object.freeze({ name: 'Charlie' })
  frozenUser.name = 'David' // 静默失败(严格模式下报错)
  console.log(frozenUser.name) // 'Charlie'
}

解构赋值

从数组或对象中提取值,赋给变量的语法糖。

{
  // 数组解构
  const [a, b, c] = [1, 2, 3]
  console.log(a, b, c) // 1 2 3

  // 跳过元素
  const [first, , third] = [1, 2, 3]
  console.log(first, third) // 1 3

  // 剩余元素
  const [head, ...tail] = [1, 2, 3, 4, 5]
  console.log(head, tail) // 1 [2, 3, 4, 5]

  // 默认值
  const [x = 1, y = 2] = [undefined, null]
  console.log(x, y) // 1 null

  // 对象解构
  const { name, age } = { name: 'Alice', age: 25, city: 'NYC' }
  console.log(name, age) // 'Alice' 25

  // 重命名
  const { name: userName, age: userAge } = { name: 'Bob', age: 30 }
  console.log(userName, userAge) // 'Bob' 30

  // 嵌套解构
  const { user: { profile: { avatar } } } = {
    user: { profile: { avatar: 'avatar.jpg' } }
  }
  console.log(avatar) // 'avatar.jpg'

  // 函数参数解构
  function greet({ name, age = 18 }) {
    console.log(`Hello ${name}, you are ${age} years old`)
  }
  greet({ name: 'Charlie' }) // Hello Charlie, you are 18 years old

  // 交换变量
  let m = 1, n = 2;
  [m, n] = [n, m]
  console.log(m, n) // 2 1

  // 字符串解构
  const [char1, char2] = 'hello'
  console.log(char1, char2) // 'h' 'e'
}

字符串扩展

Unicode表示法

{
  console.log("a", "\u0061"); // a a
  console.log("s", "\u20BB7"); // s ₻7

  console.log("s", "\u{20BB7}"); // s 𠮷
}

模板字符串

{
  // 标签模板
  let user = {
    name: "wcd",
    info: "hello world"
  };
  console.log(abc`i am ${user.name},${user.info}`); // i am ,,,wcdhello world
  function abc(s, v1, v2) {
    console.log(s, v1, v2); // [ 'i am ', ',', '' ] 'wcd' 'hello world' 
    return s + v1 + v2;
  }
}

{
  // String.raw 方法往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。
  console.log(String.raw`Hi\n${1 + 2}`); // Hi\n3
  console.log(`Hi\n${1 + 2}`); // Hi 3
}

String.prototype.fromCodePoint()

{
  // es5
  console.log(String.fromCharCode("0x20bb7")); // ஷ
  // es6
  console.log(String.fromCodePoint("0x20bb7")); // 𠮷


  let str = "\u{20bb7}abc";

  // es5
  for (let i = 0; i < str.length; i++) {
    console.log("es5", str[i]);
  }
  // es5 �
  // es5 �
  // es5 a
  // es5 b
  // es5 c

  // es6
  for (let code of str) {
    console.log("es6", code);
  }
  // es6 𠮷
  // es6 a
  // es6 b
  // es6 c
}

String.prototype.includes()/startsWith/endsWith

{
  // includes():返回布尔值,表示是否找到了参数字符串。
  // startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  // endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  let str = "string";
  console.log("includes", str.includes("r")); // includes true
  console.log("includes", str.includes("c")); // includes false

  console.log("start", str.startsWith("str")); // start true
  console.log("end", str.endsWith("str")); // end false
}

String.prototype.repeat()

{
  // repeat 方法返回一个新字符串,表示将原字符串重复n次。
  let str = "abc";
  console.log(str.repeat(2)); // abcabc
  console.log('na'.repeat(0);); // ""

  // 参数如果是小数,会被取整。
  console.log('na'.repeat(2.9)); // "nana"

  // 如果repeat的参数是负数或者Infinity,会报错。
  console.log('na'.repeat(Infinity)); // RangeError
  console.log('na'.repeat(-1)); // RangeError

  // but 如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。
  console.log('na'.repeat(-0.9)); // ""

  // 参数NaN等同于 0。
  console.log('na'.repeat(NaN)); // ""

  // 如果repeat的参数是字符串,则会先转换成数字。
  console.log('na'.repeat('na')); // ""
  console.log('na'.repeat('3')); // "nanana"
}

String.raw

// String.raw
console.log(String.raw`Hello\nWorld`) // Hello\nWorld (不转义)
console.log(`Hello\nWorld`)           // Hello
                                      // World (转义)

数值扩展

新的数值表示法和数学方法。

二进制0b(或0B) 八进制0o(或0O)

{
  // es5
  // 十进制 -> 二进制
  const a = 5
  console.log(a.toString(2)) // 101

  // 二进制 -> 十进制
  const b = 101
  console.log(parseInt(b, 2)) // 5
}

{
  // es6
  // 0b(或0B) 和0o(或0O)
  const a = 0b0101
  console.log(a)

  const b = 0o777
  console.log(b)
}

Number.isFinite()

用来检查一个数值是否为有限的(finite),即不是Infinity

{
  console.log(Number.isFinite(15)) // true
  console.log(Number.isFinite(0.8)) // true
  console.log(Number.isFinite(NaN)) // false
  console.log(Number.isFinite(Infinity)) // false
  console.log(Number.isFinite(-Infinity)) // false
  console.log(Number.isFinite('foo')) // false
  console.log(Number.isFinite('15')) // false
  console.log(Number.isFinite(true)) // false
}

Number.isNaN()

用来检查一个值是否为NaN

{
  console.log(Number.isNaN(NaN)) // true
  console.log(Number.isNaN(15)) // false
  console.log(Number.isNaN('15')) // false
  console.log(Number.isNaN(true)) // false
  console.log(Number.isNaN(9 / NaN)) // false
  console.log(Number.isNaN('true' / 0)) // true
  console.log(Number.isNaN('true' / 'true')) // true
}

Number.parseInt()

  • Number.parseInt() 方法依据指定基数解析成整数。
  • 注:
    • 这个方法和全局的 parseInt() 函数具有一样的函数功能
    • Number.parseInt === parseInt; // true
    • ES6 将全局方法parseInt()移植到Number对象上面,其目的是对全局变量进行模块化
{
  // ES5的写法
  console.log(parseInt('12.34')) // 12

  // ES6的写法
  console.log(Number.parseInt('12.34')) // 12
}

Number.parseFloat()

Number.parseFloat() 方法可以把一个字符串解析成浮点数。该方法与全局的

parseFloat() 函数相同,并且处于 ECMAScript 6 规范中(用于全局变量的模块化)

{
  // ES5的写法
  console.log(parseFloat('12.34#')) // 12.34

  // ES6的写法
  console.log(Number.parseFloat('12.34#')) // 12.34
}

Number.isInteger()

Number.isInteger() 方法用来判断给定的参数是否为整数

{
  console.log(Number.isInteger(25)) // true
  console.log(Number.isInteger(25.1)) // false
  console.log(Number.isInteger(null)) // false
  console.log(Number.isInteger('15')) // false
  console.log(Number.isInteger(true)) // false
}

Number.MAX_SAFE_INTEGER

Number.MAX_SAFE_INTEGER 常量表示在 JavaScript 中最大的安全整数(maxinum safe integer)(253 - 1)

{
  console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
  console.log(Math.pow(2, 53) - 1) // 9007199254740991
}

Number.MIN_SAFE_INTEGER

Number.MIN_SAFE_INTEGER 代表在 JavaScript中最小的安全的integer型数字 (-(253 - 1))

{
  console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991
  console.log(-Math.pow(2, 53) - 1) // -9007199254740991
}

Number.isSafeInteger()

Number.isSafeInteger() 方法用来判断传入的参数值是否是一个“安全整数”(safe integer)

{
  console.log(Number.isSafeInteger(3)) // true
  console.log(Number.isSafeInteger(Math.pow(2, 53))) // true
  console.log(Number.isSafeInteger(Math.pow(2, 53) - 1)) // true
  console.log(Number.isSafeInteger(NaN)) // false
  console.log(Number.isSafeInteger(Infinity)) // false
  console.log(Number.isSafeInteger('3')) // false
  console.log(Number.isSafeInteger(3.1)) // false
  console.log(Number.isSafeInteger(3.1)) // false
  console.log(Number.isSafeInteger(3.0)) // true
}

Math扩展

Math.trunc()

Math.trunc() 方法会将数字的小数部分去掉,只保留整数部分

{
  console.log(Math.trunc(13.37)) // 13
  console.log(Math.trunc(42.84)) // 42
  console.log(Math.trunc(0.123)) // 0
  console.log(Math.trunc(-0.123)) // 0
  console.log(Math.trunc(true)) // 1
  console.log(Math.trunc(false)) // 0
  console.log(Math.trunc(NaN)) // NaN
  console.log(Math.trunc(undefined)) // NaN
  console.log(Math.trunc('foo')) // NaN
  console.log(Math.trunc()) // NaN
}
Math.sign()

Math.sign() 函数返回一个数字的符号, 指示数字是正数,负数还是零 注: 此函数共有5种返回值, 分别是 1, -1, 0, -0, NaN. 代表的各是正数, 负数, 正零, 负零, NaN 传入该函数的参数会被隐式转换成数字类型

{
  console.log(Math.sign(3)) // 1
  console.log(Math.sign(-3)) // -1
  console.log(Math.sign('-3')) // -1
  console.log(Math.sign(0)) //  0
  console.log(Math.sign(-0)) // -0
  console.log(Math.sign(NaN)) // NaN
  console.log(Math.sign(true)) // 1
  console.log(Math.sign(false)) // 0
  console.log(Math.sign('foo')) // NaN
  console.log(Math.sign()) // NaN
}
Math.cbrt()

Math.cbrt() 函数返回任意数字的立方根

{
  console.log(Math.cbrt(8)) // 2
  console.log(Math.cbrt(NaN)) // NaN
  console.log(Math.cbrt(-1)) // -1
  console.log(Math.cbrt(-0)) // -0
  console.log(Math.cbrt(-Infinity)) // -Infinity
  console.log(Math.cbrt(0)) // 0
  console.log(Math.cbrt(1)) // 1
  console.log(Math.cbrt(Infinity)) // Infinity
  console.log(Math.cbrt(null)) // 0
  console.log(Math.cbrt(2)) // 1.2599210498948734
}

数组扩展 (类数组/伪数组)

Array.from()

{
  // Array.from() 方法从一个类似数组或可迭代对象中创建一个新的数组实例。
  let p = document.querySelectorAll('p')
  let pArr = Array.from(p)
  pArr.forEach(function (item) {
    console.log(item.textContent) //
  })

  console.log(
    Array.from([1, 3, 5], function (item) {
      return item * 2
    })
  ) // [ 2, 6, 10 ]
}

Array.of()

{
  // Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
  let arr = Array.of(3, 4, 7, 9, 11)
  console.log('arr=', arr) // arr= [ 3, 4, 7, 9, 11 ]

  let empty = Array.of()
  console.log('empty', empty) // empty []
}

Array.copyWithin()

{
  // copyWithin函数,用于操作当前数组自身,用来把某些个位置的元素复制并覆盖到其他位置上去

  // Array.prototype.copyWithin(target, start = 0, end = this.length)
  // target:目的起始位置。
  // start:复制源的起始位置,可以省略,可以是负数。
  // end:复制源的结束位置,可以省略,可以是负数,实际结束位置是end-1。

  console.log([1, 2, 3, 4, 5].copyWithin(0, 3, 4)) // [ 4, 2, 3, 4, 5 ]
}

Array.fill()

{
  // fill()函数,使用制定的元素填充数组,其实就是用默认内容初始化数组

  // arr.fill(value, start, end)
  // value:填充值
  // start:填充起始位置,可以省略
  // end:填充结束位置,可以省略,实际结束位置是end-1
  console.log('fill-7', [1, 'a', undefined].fill(7)) // fill-7 [ 7, 7, 7 ]
  console.log('fill,pos', ['a', 'b', 'c'].fill(7, 1, 3)) // fill,pos [ 'a', 7, 7 ]
}

Array.find()/Array.findIndex()

{
  // 数组实例的find方法,用于找出符合条件的数组的值
  // 数组实例的findIndex方法,用于找出符合条件的数组下标
  console.log(
    [1, 2, 3, 4, 5, 6].find(function (item) {
      return item > 3
    })
  ) // 4
  console.log(
    [1, 2, 3, 4, 5, 6].findIndex(function (item) {
      return item > 3
    })
  ) // 3
}

Array.includes()

{
  // ES6提供了Array.includes()函数判断是否包含某一元素,除了不能定位外,解决了indexOf的上述的两个问题。它直接返回true或者false表示是否包含元素,对NaN一样能有有效。
  console.log('number', [1, 2, NaN].includes(1)) // number true
  console.log('number', [1, 2, NaN].includes(NaN)) // number true
}

Array.entries/Array.keys/Array.values

{
  // ES6 提供三个新的方法 —— entries(),keys()和values() —— 用于遍历数组。它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历

  // values() 方法在 Chrome 下面运行会报错未定义(not a function)

  for (let index of ['1', 'c', 'ks'].keys()) {
    console.log('keys', index)
    // keys 0
    // keys 1
    // keys 2
  }

  for (let values of ['1', 'c', 'ks']) {
    console.log('values', values)
    // values 1
    // values c
    // values ks
  }

  for (let [index, values] of ['1', 'c', 'ks'].entries()) {
    console.log('index,values', index, values)
    // index,values 0 1
    // index,values 1 c
    // index,values 2 ks
  }
}

函数扩展

参数默认值、rest 参数、扩展运算符和箭头函数。

箭头函数和普通函数的区别
  1. 箭头函数和普通函数的样式不同,箭头函数语法更加简洁、清晰,箭头函数是=>定义函数,普通函数是function定义函数;
  2. 箭头函数会捕获其所在上下文的 this 值,作为自己的 this 值,定义的时候就确定并固定了;
  3. 箭头函数不能作为构造函数使用,也不能使用new关键字(因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会改变,作为构造函数其的this要是指向创建的新对象);
  4. 箭头函数没有自己的arguments。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值;
  5. call、apply、bind 并不会影响其 this 的指向;
  6. 箭头函数没有原型prototype;
  7. 箭头函数不能当作 Generator 函数,不能使用 yield 关键字;
{
  // 参数默认值
  function greet(name = 'World', age = 18) {
    console.log(`Hello ${name}, age ${age}`)
  }
  greet() // Hello World, age 18
  greet('Alice', 25) // Hello Alice, age 25

  // 默认值可以是表达式
  function createId(prefix = 'user', suffix = Date.now()) {
    return `${prefix}_${suffix}`
  }

  // rest 参数
  function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0)
  }
  console.log(sum(1, 2, 3, 4)) // 10

  // 扩展运算符
  const arr1 = [1, 2, 3]
  const arr2 = [4, 5, 6]
  const combined = [...arr1, ...arr2]
  console.log(combined) // [1, 2, 3, 4, 5, 6]

  // 函数调用
  function multiply(a, b, c) {
    return a * b * c
  }
  console.log(multiply(...[2, 3, 4])) // 24

  // 箭头函数
  const double = x => x * 2
  const add = (a, b) => a + b
  const createUser = name => ({ name, createdAt: new Date() })

  // 箭头函数的 this
  const obj = {
    name: 'Alice',
    greet: function() {
      setTimeout(() => {
        console.log(`Hello, I'm ${this.name}`) // this 指向 obj
      }, 100)
    }
  }

  // 尾调用优化
  function factorial(n, acc = 1) {
    if (n <= 1) return acc
    return factorial(n - 1, n * acc) // 尾调用
  }
}

对象扩展

简洁表示法、计算属性名和新的对象方法。

{
  // 简洁表示法
  const name = 'Alice'
  const age = 25
  
  const user = {
    name,        // 等同于 name: name
    age,         // 等同于 age: age
    greet() {    // 等同于 greet: function() {}
      console.log(`Hello, I'm ${this.name}`)
    }
  }

  // 计算属性名
  const prop = 'foo'
  const obj = {
    [prop]: 'bar',
    ['hello' + 'world']: 123,
    [Symbol.iterator]: function() {} // symbol 作为属性名
  }

  // Object.is()
  console.log(Object.is(NaN, NaN))     // true
  console.log(Object.is(+0, -0))       // false
  console.log(NaN === NaN)             // false
  console.log(+0 === -0)               // true

  // Object.assign()
  const target = { a: 1, b: 2 }
  const source1 = { b: 3, c: 4 }
  const source2 = { c: 5, d: 6 }
  
  Object.assign(target, source1, source2)
  console.log(target) // { a: 1, b: 3, c: 5, d: 6 }

  // 克隆对象
  const clone = Object.assign({}, user)

  // 合并对象
  const merged = Object.assign({}, source1, source2)

  // 对象的遍历
  const data = { name: 'Bob', age: 30 }
  
  // for...in
  for (const key in data) {
    console.log(key, data[key])
  }

  // Object.keys()
  Object.keys(data).forEach(key => {
    console.log(key, data[key])
  })

  // Object.getOwnPropertyNames()
  Object.getOwnPropertyNames(data).forEach(key => {
    console.log(key, data[key])
  })

  // 扩展运算符(ES2018)
  const { name: userName, ...rest } = user
  const newUser = { ...user, city: 'NYC' }
}

Symbol

新的原始数据类型,表示独一无二的值。

{
  // 基本用法
  const sym1 = Symbol()
  const sym2 = Symbol('description')
  const sym3 = Symbol('description')

  console.log(sym2 === sym3) // false,每个 Symbol 都是唯一的

  // Symbol.for() 全局注册
  const globalSym1 = Symbol.for('global')
  const globalSym2 = Symbol.for('global')
  console.log(globalSym1 === globalSym2) // true

  // Symbol.keyFor() 获取描述
  console.log(Symbol.keyFor(globalSym1)) // 'global'
  console.log(Symbol.keyFor(sym2))       // undefined

  // 作为对象属性
  const obj = {
    [sym2]: 'symbol property',
    normalProp: 'normal property'
  }

  console.log(obj[sym2])        // 'symbol property'
  console.log(obj.normalProp)   // 'normal property'

  // Symbol 属性不被常规方法遍历
  console.log(Object.keys(obj))                 // ['normalProp']
  console.log(Object.getOwnPropertyNames(obj))  // ['normalProp']
  console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(description)]

  // Reflect.ownKeys() 可以返回所有属性
  console.log(Reflect.ownKeys(obj)) // ['normalProp', Symbol(description)]

  // 内置 Symbol
  const arr = [1, 2, 3]
  console.log(arr[Symbol.iterator]) // 数组的迭代器

  // 自定义迭代器
  const iterable = {
    [Symbol.iterator]() {
      let count = 0
      return {
        next() {
          if (count < 3) {
            return { value: count++, done: false }
          }
          return { done: true }
        }
      }
    }
  }

  for (const value of iterable) {
    console.log(value) // 0 1 2
  }
}

Set 和 Map

新的数据结构,提供更好的集合操作。

{
  // Set - 值的集合,值是唯一的
  const set = new Set([1, 2, 3, 3, 4])
  console.log(set) // Set {1, 2, 3, 4}

  // Set 方法
  set.add(5)
  console.log(set.has(3))    // true
  console.log(set.size)      // 5
  set.delete(2)
  console.log(set)           // Set {1, 3, 4, 5}

  // 数组去重
  const arr = [1, 2, 2, 3, 3, 4]
  const unique = [...new Set(arr)]
  console.log(unique) // [1, 2, 3, 4]

  // Set 遍历
  for (const value of set) {
    console.log(value)
  }

  set.forEach(value => console.log(value))

  // WeakSet - 弱引用,只能存储对象
  const weakSet = new WeakSet()
  const obj1 = {}
  const obj2 = {}
  
  weakSet.add(obj1)
  weakSet.add(obj2)
  console.log(weakSet.has(obj1)) // true

  // Map - 键值对集合,键可以是任意类型
  const map = new Map()
  const keyObj = {}
  const keyFunc = function() {}

  map.set('string', 'string value')
  map.set(keyObj, 'object value')
  map.set(keyFunc, 'function value')

  console.log(map.get('string'))  // 'string value'
  console.log(map.get(keyObj))    // 'object value'
  console.log(map.size)           // 3

  // Map 构造函数
  const map2 = new Map([
    ['name', 'Alice'],
    ['age', 25]
  ])

  // Map 遍历
  for (const [key, value] of map2) {
    console.log(key, value)
  }

  map2.forEach((value, key) => {
    console.log(key, value)
  })

  // WeakMap - 弱引用,键必须是对象
  const weakMap = new WeakMap()
  const key = {}
  
  weakMap.set(key, 'value')
  console.log(weakMap.get(key)) // 'value'

  // 数据结构比较
  // Map vs Object
  const mapDS = new Map()
  const objDS = {}
  
  // 性能对比示例
  mapDS.set('key1', 'value1')
  objDS.key1 = 'value1'
  
  console.log(mapDS.has('key1'))    // true
  console.log('key1' in objDS)      // true
}

Iterator 和 for...of

统一的遍历接口和新的遍历语法。

{
  // 数组的迭代器
  const arr = ['a', 'b', 'c']
  const iterator = arr[Symbol.iterator]()

  console.log(iterator.next()) // { value: 'a', done: false }
  console.log(iterator.next()) // { value: 'b', done: false }
  console.log(iterator.next()) // { value: 'c', done: false }
  console.log(iterator.next()) // { value: undefined, done: true }

  // for...of 循环
  for (const value of arr) {
    console.log(value) // 'a' 'b' 'c'
  }

  // 字符串迭代
  for (const char of 'hello') {
    console.log(char) // 'h' 'e' 'l' 'l' 'o'
  }

  // 自定义迭代器
  const range = {
    from: 1,
    to: 5,
    [Symbol.iterator]() {
      let current = this.from
      const last = this.to
      return {
        next() {
          if (current <= last) {
            return { value: current++, done: false }
          } else {
            return { done: true }
          }
        }
      }
    }
  }

  for (const num of range) {
    console.log(num) // 1 2 3 4 5
  }

  // 类数组对象添加迭代器
  const arrayLike = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
  }

  for (const value of arrayLike) {
    console.log(value) // 'a' 'b' 'c'
  }
}

Generator

生成器函数,可以暂停和恢复执行的函数。

{
  // 基本语法
  function* simpleGenerator() {
    yield 1
    yield 2
    yield 3
  }

  const gen = simpleGenerator()
  console.log(gen.next()) // { value: 1, done: false }
  console.log(gen.next()) // { value: 2, done: false }
  console.log(gen.next()) // { value: 3, done: false }
  console.log(gen.next()) // { value: undefined, done: true }

  // 无限序列
  function* fibonacci() {
    let a = 0, b = 1
    while (true) {
      yield a;
      [a, b] = [b, a + b]
    }
  }

  const fib = fibonacci()
  console.log(fib.next().value) // 0
  console.log(fib.next().value) // 1
  console.log(fib.next().value) // 1
  console.log(fib.next().value) // 2

  // yield* 委托
  function* inner() {
    yield 1
    yield 2
  }

  function* outer() {
    yield 0
    yield* inner()
    yield 3
  }

  console.log([...outer()]) // [0, 1, 2, 3]

  // 双向通信
  function* twoWayGen() {
    const input1 = yield 'First yield'
    const input2 = yield 'Second yield'
    return `Inputs: ${input1}, ${input2}`
  }

  const gen2 = twoWayGen()
  console.log(gen2.next())        // { value: 'First yield', done: false }
  console.log(gen2.next('hello')) // { value: 'Second yield', done: false }
  console.log(gen2.next('world')) // { value: 'Inputs: hello, world', done: true }

  // 实际应用:异步流程控制
  function* asyncFlow() {
    try {
      const user = yield fetchUser()
      const posts = yield fetchPosts(user.id)
      return { user, posts }
    } catch (error) {
      console.error('Error:', error)
    }
  }

  function fetchUser() {
    return Promise.resolve({ id: 1, name: 'Alice' })
  }

  function fetchPosts(userId) {
    return Promise.resolve([{ id: 1, title: 'Post 1' }])
  }

  // 执行异步生成器
  function runAsync(genFunc) {
    const gen = genFunc()
    
    function step(value) {
      const result = gen.next(value)
      if (result.done) {
        return result.value
      }
      return Promise.resolve(result.value).then(step)
    }
    
    return step()
  }

  runAsync(asyncFlow).then(result => {
    console.log(result) // { user: {...}, posts: [...] }
  })
}

Promise

异步编程的新解决方案,比传统的回调函数更合理和强大。

{
  // 基本用法
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve('Success!')
      } else {
        reject(new Error('Failed!'))
      }
    }, 1000)
  })

  promise
    .then(result => console.log(result))
    .catch(error => console.error(error))

  // 链式调用
  fetchUser()
    .then(user => {
      console.log('User:', user)
      return fetchPosts(user.id)
    })
    .then(posts => {
      console.log('Posts:', posts)
      return posts.map(post => post.title)
    })
    .then(titles => {
      console.log('Titles:', titles)
    })
    .catch(error => {
      console.error('Error:', error)
    })

  function fetchUser() {
    return new Promise(resolve => {
      setTimeout(() => resolve({ id: 1, name: 'Alice' }), 100)
    })
  }

  function fetchPosts(userId) {
    return new Promise(resolve => {
      setTimeout(() => resolve([
        { id: 1, title: 'Post 1', userId },
        { id: 2, title: 'Post 2', userId }
      ]), 100)
    })
  }

  // Promise.all() - 并行执行
  Promise.all([
    fetch('/api/users'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]).then(responses => {
    console.log('All requests completed')
    return Promise.all(responses.map(r => r.json()))
  }).then(data => {
    console.log('All data:', data)
  })

  // Promise.race() - 竞态
  Promise.race([
    new Promise(resolve => setTimeout(() => resolve('Fast'), 100)),
    new Promise(resolve => setTimeout(() => resolve('Slow'), 500))
  ]).then(result => {
    console.log(result) // 'Fast'
  })

  // 错误处理
  new Promise((resolve, reject) => {
    throw new Error('Something went wrong')
  })
  .then(result => {
    console.log(result) // 不会执行
  })
  .catch(error => {
    console.error('Caught:', error.message)
  })
  .finally(() => {
    console.log('Cleanup') // 总是执行
  })

  // Promise 状态
  console.log(Promise.resolve('resolved'))  // 立即解决
  console.log(Promise.reject('rejected'))   // 立即拒绝
}

Class

更清晰的面向对象编程语法糖。

{
  // 基本类定义
  class Person {
    constructor(name, age) {
      this.name = name
      this.age = age
    }

    greet() {
      console.log(`Hello, I'm ${this.name}`)
    }

    get info() {
      return `${this.name} is ${this.age} years old`
    }

    set info(value) {
      const [name, age] = value.split(' is ')
      this.name = name
      this.age = parseInt(age)
    }

    static species() {
      return 'Homo sapiens'
    }
  }

  const person = new Person('Alice', 25)
  person.greet() // Hello, I'm Alice
  console.log(person.info) // Alice is 25 years old
  console.log(Person.species()) // Homo sapiens

  // 继承
  class Student extends Person {
    constructor(name, age, school) {
      super(name, age) // 调用父类构造函数
      this.school = school
    }

    greet() {
      super.greet() // 调用父类方法
      console.log(`I study at ${this.school}`)
    }

    study() {
      console.log(`${this.name} is studying`)
    }
  }

  const student = new Student('Bob', 20, 'MIT')
  student.greet() 
  // Hello, I'm Bob
  // I study at MIT
  student.study() // Bob is studying

  // 静态属性(ES2022)
  class MyClass {
    static staticProperty = 'static value'
    
    static staticMethod() {
      return 'static method'
    }
  }

  console.log(MyClass.staticProperty) // 'static value'
  console.log(MyClass.staticMethod()) // 'static method'

  // 私有字段和方法(ES2022)
  class PrivateExample {
    #privateField = 'private'
    
    #privateMethod() {
      return 'private method'
    }
    
    publicMethod() {
      return this.#privateMethod()
    }
  }

  const privateExample = new PrivateExample()
  console.log(privateExample.publicMethod()) // 'private method'
  // console.log(privateExample.#privateField) // SyntaxError
}

Proxy 和 Reflect

对象操作的拦截和自定义。

{
  // Proxy 基本用法
  const target = {
    name: 'Alice',
    age: 25
  }

  const proxy = new Proxy(target, {
    get(target, property) {
      console.log(`Getting ${property}`)
      return target[property]
    },
    
    set(target, property, value) {
      console.log(`Setting ${property} to ${value}`)
      if (property === 'age' && value < 0) {
        throw new Error('Age cannot be negative')
      }
      target[property] = value
      return true
    },
    
    has(target, property) {
      console.log(`Checking if ${property} exists`)
      return property in target
    },
    
    deleteProperty(target, property) {
      console.log(`Deleting ${property}`)
      if (property.startsWith('_')) {
        delete target[property]
        return true
      }
      return false
    }
  })

  console.log(proxy.name)    // Getting name, Alice
  proxy.age = 30             // Setting age to 30
  console.log('name' in proxy) // Checking if name exists, true

  // Reflect - 提供拦截 JavaScript 操作的方法
  const obj = { name: 'Bob', age: 20 }
  
  console.log(Reflect.get(obj, 'name'))        // Bob
  Reflect.set(obj, 'age', 25)
  console.log(Reflect.has(obj, 'name'))        // true
  console.log(Reflect.ownKeys(obj))            // ['name', 'age']

  // 验证器示例
  function createValidator(target, validators) {
    return new Proxy(target, {
      set(target, property, value) {
        const validator = validators[property]
        if (validator && !validator(value)) {
          throw new Error(`Invalid value for ${property}`)
        }
        return Reflect.set(target, property, value)
      }
    })
  }

  const userValidators = {
    name: val => typeof val === 'string' && val.length > 0,
    age: val => typeof val === 'number' && val >= 0
  }

  const validatedUser = createValidator({}, userValidators)
  validatedUser.name = 'Alice' // OK
  validatedUser.age = 25       // OK
  // validatedUser.age = -1     // Error: Invalid value for age
}

模块化

ES6 原生支持模块化,提供 import/export 语法。

{
  // 导出方式
  
  // 命名导出
  export const PI = 3.14159
  export function add(a, b) {
    return a + b
  }
  export class Calculator {
    multiply(a, b) {
      return a * b
    }
  }

  // 批量导出
  const name = 'MyModule'
  const version = '1.0.0'
  function init() {
    console.log('Module initialized')
  }
  
  export { name, version, init }

  // 重命名导出
  export { init as initialize, name as moduleName }

  // 默认导出
  export default class DefaultClass {
    constructor() {
      console.log('Default class created')
    }
  }

  // 或者
  class MyClass {}
  export default MyClass

  // 导入方式
  
  // 命名导入
  import { PI, add, Calculator } from './math.js'
  
  // 重命名导入
  import { add as sum, PI as pi } from './math.js'
  
  // 默认导入
  import DefaultClass from './module.js'
  
  // 混合导入
  import DefaultClass, { PI, add } from './module.js'
  
  // 全部导入
  import * as mathModule from './math.js'
  console.log(mathModule.PI)
  console.log(mathModule.add(1, 2))
  
  // 动态导入
  async function loadModule() {
    const module = await import('./math.js')
    console.log(module.PI)
  }
  
  // 或者
  import('./math.js').then(module => {
    console.log(module.add(1, 2))
  })

  // 只执行模块,不导入
  import './init.js'

  // 转发导出
  export { PI, add } from './math.js'
  export { default } from './defaultModule.js'
  export * from './utilities.js'
}

Decorator(装饰器)

函数修饰符,用于扩展类和方法的功能(Stage 3 提案)。

{
  // 方法装饰器
  function readonly(target, name, descriptor) {
    descriptor.writable = false
    return descriptor
  }

  function log(type) {
    return function(target, name, descriptor) {
      const originalMethod = descriptor.value
      descriptor.value = function(...args) {
        console.log(`[${type}] Calling ${name} with args:`, args)
        const result = originalMethod.apply(this, args)
        console.log(`[${type}] Result:`, result)
        return result
      }
      return descriptor
    }
  }

  class Example {
    @readonly
    getValue() {
      return 'read-only value'
    }

    @log('INFO')
    calculate(a, b) {
      return a + b
    }
  }

  const example = new Example()
  console.log(example.getValue()) // read-only value
  example.calculate(2, 3)         // [INFO] Calling calculate..., [INFO] Result: 5

  // 类装饰器
  function addMetadata(target) {
    target.metadata = { createdAt: new Date() }
    return target
  }

  @addMetadata
  class MetadataClass {
    constructor(name) {
      this.name = name
    }
  }

  console.log(MetadataClass.metadata) // { createdAt: Date }

  // 实际应用:埋点装饰器
  function track(eventName) {
    return function(target, name, descriptor) {
      const originalMethod = descriptor.value
      descriptor.value = function(...args) {
        // 埋点逻辑
        console.log(`Track event: ${eventName}`)
        return originalMethod.apply(this, args)
      }
      return descriptor
    }
  }

  class Button {
    @track('button_click')
    onClick() {
      console.log('Button clicked')
    }

    @track('button_hover')
    onHover() {
      console.log('Button hovered')
    }
  }

  const button = new Button()
  button.onClick() // Track event: button_click, Button clicked
}
Copyright (c) 2023-PRESENT | wudi