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