ECMA新特性(部分)

发布时间 2023-06-28 14:54:24作者: lix_uan

ES2015

  • 数组的解构

    const arr = [10, 20, 30]
    
    const [, , c] = arr
    console.log(c) // 30
    
    const [foo, ...rest] = arr
    console.log(rest) // [20, 30]
    
    const [a, b, c, d = 'default'] = arr
    console.log(b, d) //20 default
    
  • 带标签的模板字符串

    const name = 'lx'
    const gender = 1
    
    const tag = (strings, name, gender) => {
      const sex = gender ? '男' : '女'
      return name + strings[1] + sex + strings[2]
    }
    
    const res = tag`${name}是一个${gender}生`
    console.log(res)
    
  • 字符串的扩展方法

    const message = 'Error: something went wrong!'
    
    console.log(message.startsWith('Error'))
    console.log(message.endsWith('.'))
    console.log(message.includes('something'))
    
  • 箭头函数

    // 箭头函数中 this 的指向不会发生改变
    Object.prototype.name = 'Object prototype'
    
    const person = {
      name: 'John Doe',
      sayHi: () => console.log(`Hello, my name is ${this.name}!`),
      sayHiAsync: function () {
        setTimeout(() => {
          console.log(`Hello, my name is ${this.name}!`)
        }, 1000)
      }
    
      // sayHiAsync: function () {
      //   setTimeout(
      //     function () {
      //       console.log(`Hello, my name is ${this.name}!`)
      //     }.bind(this),
      //     1000
      //   )
      // }
    }
    
    person.sayHi() // Object prototype
    person.sayHiAsync() // John Doe
    
  • 对象字面量增强

    const foo = '123'
    
    const obj = {
      foo,
      method() {
        console.log(this)
      },
      [Math.random()]: 123 // 计算属性名
    }
    
    obj.method() // { foo: '123', method: [Function: method], '0.5063349741642842': 123 }
    
  • Object.assign

    const source = {
      a: 123,
      b: 456
    }
    
    const target = {
      a: 456,
      c: 789
    }
    
    const result = Object.assign(target, source)
    console.log(result)
    console.log(target === result)
    
  • Proxy

    // 更好的支持数组对象的监视
    // 以非侵入的方式监管了对象的读写
    
    const person = {
      name: 'John Doe',
      age: 25
    }
    
    const personProxy = new Proxy(person, {
      get(target, property) {
        return property in target ? target[property] : 'Non-existent'
      },
      set(target, property, value) {
        if (property === 'age' && typeof value !== 'number') {
          throw new TypeError('Age must be a number')
        }
    
        target[property] = value
      }
    })
    
    console.log(personProxy.name) // John Doe
    personProxy.age = 'Thirty' // Uncaught TypeError: Age must be a number
    
  • Reflect

    // Reflect内部封装了一些列对对象底层的操作
    // Reflect成员方法就是Proxy处理对象的默认实现
    
    const person = {
      name: 'John Doe',
      age: 25
    }
    
    const personProxy = new Proxy(person, {
      get(target, property) {
        console.log('get方法被调用')
        Reflect.get(target, property) // 如果没有定义get方法,默认调用Reflect.get方法
      }
    })
    
    
    // 意义:提供了一套用于操作对象的API
    
    // 之前
    console.log('name' in person)
    console.log(delete person.name)
    console.log(Object.keys(person))
    
    // 使用Reflect之后
    console.log(Reflect.has(person, 'name'))
    console.log(Reflect.deleteProperty(person, 'name'))
    console.log(Reflect.ownKeys(person))
    
  • Symbol

    const s = Symbol('foo')
    const f = Symbol('foo')
    console.log(s === f) // false
    
    const s1 = Symbol.for('foo')
    const f1 = Symbol.for('foo')
    console.log(s1 === f1) // true
    
    const val = Symbol('foo')
    const obj = {
      [val]: 'foo val',
      print() {
        console.log(this[val])
      }
    }
    
    // Symbol适合用作对象的私有属性
    // 无法拿到 Symbol 属性
    console.log(Object.keys(obj))
    console.log(JSON.stringify(obj))
    for(let key in obj) console.log(key)
    
    // 需要使用 Object.getOwnPropertySymbols
    console.log(Object.getOwnPropertySymbols(obj))
    
    obj.print() // foo val
    
  • for...of

    // for..of只能遍历实现了Iteratble接口,不能遍历对象
    
    const arr = [1, 2, 3, 4, 5]
    
    for (const item of arr) {
      if (item === 3) {
        console.log('Found it!')
        break // for...of 可以随时终止循环,forEach 不行
      }
      console.log(item)
    }
    
    // for...of 遍历set
    const s = new Set([1, 2, 3, 4, 5])
    for (const item of s) {
      console.log(item)
    }
    
    // for...of 遍历map
    const m = new Map()
    m.set('foo', 'bar')
    m.set('hello', 'world')
    for (const [key, value] of m) {
      console.log(key, value)
    }
    
  • 迭代器(Iterator)

    // 自定义对象实现可迭代接口
    const obj = {
      store: ['foo', 'bar', 'baz'],
    
      [Symbol.iterator]: function () {
        let index = 0
        const self = this
    
        return {
          next: function () {
            const res = {
              value: self.store[index],
              done: index >= self.store.length
            }
            index++
            return res
          }
        }
      }
    }
    
    for (const item of obj) {
      console.log(item)
    }
    
    
    // 补充:迭代器模式
    const todo = {
      life: ['吃饭', '睡觉', '打豆豆'],
      learn: ['语文', '数学', '英语'],
      work: ['喝茶'],
    
      // 传统方式
      each: function (callback) {
        const all = [].concat(this.life, this.learn, this.work)
        for (const item of all) {
          callback(item)
        }
      },
    
      // 迭代器模式
      [Symbol.iterator]: function () {
        const all = [...this.life, ...this.learn, ...this.work]
        let index = 0
        return {
          next: function () {
            return {
              value: all[index],
              done: index++ >= all.length
            }
          }
        }
      }
    }
    
    todo.each(item => console.log(item))
    
    for (const item of todo) {
      console.log(item)
    }
    
  • 生成器(Generator)

    // 使用Generator实现iterator方法
    const todos = {
      life: ['吃饭', '睡觉', '打豆豆'],
      learn: ['语文', '数学', '英语'],
      work: ['喝茶'],
      [Symbol.iterator]: function* () {
        const all = [...this.life, ...this.learn, ...this.work]
        for (const item of all) {
          yield item
        }
      }
    }
    
    for (const item of todos) {
      console.log(item)
    }
    

ES2016

// Array.prototype.includes()
const arr = ['foo', 1, NaN, false]

console.log(arr.indexOf(NaN)) // indexOf can't find NaN
console.log(arr.includes(NaN)) // true

// 指数运算符
console.log(2 ** 3)

ES2017

const obj = {
  foo: 'fooVal',
  bar: 'barVal'
}

// Object.values
console.log(Object.values(obj)) // [ 'foo', 'bar' ]

// Object.entries
console.log(Object.entries(obj)) // [ [ 'foo', 'fooVal' ], [ 'bar', 'barVal' ] ]

// Object.getOwnPropertyDescriptors
console.log(Object.getOwnPropertyDescriptors(obj)) 

/* bar: {
  value: 'barVal',
  writable: true,
  enumerable: true,
  configurable: true
} */

// String.prototype.padStart / String.prototype.padEnd
const str = 'foo'
console.log(str.padStart(10, '0')) // 0000000foo
console.log(str.padEnd(10, '0')) // foo0000000

// 可以在函数参数中添加尾逗号
// Async/ Await

ES2018

  • Promise.finally

    // Promise.finally 能保证无论执行成功或失败都一定被执行,可以用来做一些清理工作
    
    const connection = { open: () => Promise.resolve() }
    connection
      .open()
      .then()
      .catch()
      .finally(() => {
        console.log('clear connection');
      })
    
  • 正则命名组捕获

    // 正则命名组捕获使用符号 ?<name> 表示,对匹配到的正则结果按名称访问
    
    const regexp = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
    const result = regexp.exec('2023-01-01');
    console.log(result.groups); // { year: '2023', month: '01', day: '01' }
    

ES2019

  • 函数的 toString() 方法

    const fn = (a, b) => {
      // return a + b value
      const c = a + b;
      return c;
    }
    
    // 返回定义的函数体代码,包含注释
    console.log(fn.toString()); 
    
  • Object.fromEntries

    // Object.fromEntries() 方法会把键值对列表转换为对象。同 Object.entries() 相反
    
    const arr = [ [ 'name', 'foo' ], [ 'age', 18 ] ];
    const obj = Object.fromEntries(arr);
    console.log(obj); // { name: 'foo', age: 18 }
    console.log(Object.entries(obj)); // [ [ 'name', 'foo' ], [ 'age', 18 ] 
    
  • 消除前后空格

    '  JavaScript  '.trim() // 'JavaScript'
    '  JavaScript  '.trimStart() // 'JavaScript  '
    '  JavaScript  '.trimEnd() // '  JavaScript'
    
  • 数组 flat()、flatMap()

    [['a'], ['b', 'bb'], [['c']]].flat(2) // [ 'a', 'b', 'bb', 'c' ]
    
    // flatMap:map() 和 flat() 方法的结合,该方法只能展开一维数组
    [['a'], ['b', 'bb'], [['c']]].flatMap(x => x) // [ 'a', 'b', 'bb', [ 'c' ] ]
    

ES2020

  • matchAll

    // String.prototype.matchAll() 会返回正则匹配的所有字符串及其位置,相比于 String.prototype.match() 返回的信息更详细
    
    const str = 'JavaScript'
    const regexp = /a/g
    console.log([...str.matchAll(regexp)]);
    
    // Output:
    [
      [ 'a', index: 1, input: 'JavaScript', groups: undefined ],
      [ 'a', index: 3, input: 'JavaScript', groups: undefined ]
    ]
    
  • import 动态导入

    // 返回的是一个 Promise 对象。只有在 ES Modules 模块规范下才支持
    
    // index-a.mjs
    export default {
      hello () {
        console.log(`hello JavaScript`);
      }
    }
    
    // index-b.mjs
    import('./index-a.mjs').then(module => {
      module.default.hello(); // hello JavaScript
    })
    
  • BigInt

    9007199254740995 // 会出现精度丢失
    9007199254740995n // BigInt 表示方式一
    BigInt('9007199254740995') // BigInt 表示方式二
    
  • Promise.allSettled

    // Promise.allSettled() 会等待所有的 Promise 对象都 结束 后在返回结果
    // Promise.all和Promise.allSettled的区别:Promise.allSettled永远不会被reject
    
    const delay = (value, ms, isReject) => new Promise((resolve, reject) => setTimeout(() => isReject ? reject(new Error(value)) : resolve(value), ms));
    const promises = [
      delay('a', 3000),
      delay('b', 2000, true),
    ];
    Promise.allSettled(promises)
      .then(res => console.log(res))
    
    // Output:
    [
      { status: 'fulfilled', value: 'a' },
      {
        status: 'rejected',
        reason: Error: b
            at Timeout._onTimeout (/index.js:1:108)
            at listOnTimeout (node:internal/timers:564:17)
            at process.processTimers (node:internal/timers:507:7)
      }
    ]
    
  • globalThis

    // 浏览器为 window、Node.js 为 global
    // 为了能够统一全局环境变量,引入了 globalThis
    
    window === globalThis // 浏览器环境
    global === globalThis // Node.js 环境
    
  • 可选链

    const obj = null;
    obj.a // TypeError: Cannot read properties of null (reading 'a')
    obj?.a // 使用可选链之后就不会报错了,会输出 undefined
    
  • 空值合并

    // 空值合并语法使用 ?? 表示,只有当左侧的值为 null 或 undefined 时才会返回右侧的值
    
    const a = 0
    a || 1 // 1
    a ?? 1 // 0
    

ES2021

  • String.prototype.replaceAll

    // 之前的 replace() 只会匹配一个
    
    console.log('JavaScript'.replaceAll('a', 'b')); // JbvbScript
    
  • Promise.any

    const delay = (value, ms) => new Promise((resolve, reject) => setTimeout(() => resolve(value), ms))
    const promises = [delay('a', 3000), delay('b', 2000), delay('c', 4000)]
    
    Promise.any(promises)
      .then(res => console.log(res)) // b
      .catch(err => console.error(err.name, err.message, err.errors)) // 全部失败时返回:AggregateError All promises were rejected [ 'a', 'b', 'c' ]
    
  • 数字分隔符

    const budget = 1_000_000_000_000;
    console.log(budget === 10 ** 12); // true
    
  • 逻辑赋值运算符

    // "Or Or Equals" (or, the Mallet operator :wink:)
    a ||= b; // a || (a = b);
    
    // "And And Equals"
    a &&= b; // a && (a = b);
    
    // "QQ Equals"
    a ??= b; // a ?? (a = b);
    
  • WeakRefs

    // WeakRef 对象允许保留对另一个对象的弱引用,而不会阻止被弱引用对象被 GC 回收
    
    const obj = { a: 1 };
    const ref = new WeakRef(obj)
    console.log(ref.deref());
    

ES2022

  • Class Fields

    class Person {
      name = 'Tom' // 允许在类最外层声明类成员
      #privateField1 = 'private field 1'; // 私有字段赋初值
      #privateField2; // 默认 undefined
      static #privateStaticField3 = 'private static field 3' // 私有静态类型字段
    
      static { //用于初始化的静态代码块
        try {
          const obj = doSomethingWith(this.x);
          this.y = obj.y;
          this.z = obj.z;
        } catch (err) {
          this.y = 'y is error';
          this.z = 'z is error';
        }
      }
    
      constructor(value) {
        this.#privateField2 = value; // 实例化时为私有字段赋值
      }
      #toString() { // 私有方法
        console.log(this.#privateField1, this.#privateField2);
      }
      print() {
        this.#toString()
      }
    }
    const p = new Person('private field 2')
    p.print()
    
  • Top-level await

    // 可以直接使用顶级 await
    // 仅支持 ES Modules
    let jQuery;
    try {
      jQuery = await import('https://cdn-a.com/jQuery');
    } catch {
      jQuery = await import('https://cdn-b.com/jQuery');
    }
    
  • 正则新增 /d 修饰符

    // 新增一个 /d 修饰符,它会返回一个 indices 属性,包含了匹配元素的开始、结束位置索引
    
    const str = 'ECMAScript_JavaScript'
    const regexp = /sc/igd // 忽略大小、全局匹配、并返回匹配元素的开始、结束位置索引
    console.log(regexp.exec(str).indices) // [ 4, 6 ]
    console.log(regexp.exec(str).indices) // [ 15, 17 ]
    
  • .at()操作符

    // 根据指定索引获取数组元素,不同的是它支持传递负数
    
    const arr = ['a', 'b', 'c']
    console.log(arr.at(0))
    console.log(arr.at(-1)) // 等价于 arr[arr.length - 1]
    
  • Object.hasOwn()

    // Object.prototype.hasOwnProperty() 方法遇到 obj = null这种情况会报错
    // Object.hasOwn() 提供了一种更安全的方法来检查对象是否具有自己的属性
    
    const person = Object.create({ name: 'Tom' })
    person.age = 18;
    console.log(Object.hasOwn(person, 'name')); // false
    console.log(Object.hasOwn(person, 'age')); // true
    
    // 遇到这种情况 hasOwnProperty 会报错
    const p1 = null
    console.log(p1.hasOwnProperty('name')); // TypeError: Cannot read properties of null (reading 'hasOwnProperty')
    

ES2023

// findLast、findLastIndex: 从数组末尾开始查找元素
const arr = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }]

// find vs findLast
console.log(arr.find(n => n.value % 2 === 1)) // { value: 1 }
console.log(arr.findLast(n => n.value % 2 === 1)) // { value: 3 }

// findIndex vs findLastIndex
console.log(arr.findIndex(n => n.value % 2 === 1)) // 0
console.log(arr.findLastIndex(n => n.value % 2 === 1)) // 2