Featured image of post ESNext 语法特性一览

ESNext 语法特性一览

ECMAScript(ES)是JavaScript的语法规范,2015年开始每年会推出一些新特性

注:该文章为持续更新状态,现已更新至 ES5~ES2022

ES简介

全称 ECMAScriptECMA国际 通过 ECMA 标准程序设计语言标准,JavaScript 是 ES 标准的实现

  • ECMA:European Computer Manufacturers Association(欧洲计算机制造商协会)
  • 评估、开发、认可电信和计算机标准,1994年改名为 ECMA 国际
  • TC39(Technical Committee 39)委员会推进维护,会员主要有(浏览器厂商,Apple,Google,Microsoft,Intel 等)

ES5(2009)

特性概览

  • 严格模式
  • JSON
  • 扩展对象
  • 数组
  • 原型
  • 字符串
  • 日期方法

说明:以上算是 JavaScript 的基本语法和使用了,这里不再赘述

ES6(2015)

let & const

  • let 声明变量
    1. 不能重复
    2. 块级作用域
    3. 不存在变量提升
  • const声明常量
    1. 一定要赋初始值
    2. 无法修改
    3. 块级作用域
    4. 可以修改对象和数组里面元素

解构赋值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
*	数组解构
*/
const animal = ['cat', 'dog', 'pig']
const [a,b,c,d] = animal // a='cat',b='dog',c='pig',d=undefined

/**
*	对象解构(ES9 支持)
*/
const person = {
    name: 'Lisa',
    age: 20,
    gender: 'female',
    parents: {
        father: 'Bob',
        mother: 'Jure'
    },
}
let { name, age, gender: sex, parents: { father}} = person
// name='Lisa',age=20,gender 重命名为 sex,sex='female',
// 获取parents对象中的father father='Bob'

模板字符串 ``

1
2
const str = 'hello'
const newStr = `2021,${str},Lisa` // newStr = '2021,hello,Lisa'

对象简化写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const str = 'hello'
const arr = ['a','b']
const obj = { name: 'Lisa' }
const func() { return null }
const myObject = {
    str, //str: 'hello'
    arr, // arr: ['a','b']
    obj, // obj: { name: 'Lisa' }
    func,// func: function() { return null }
}

箭头函数

1
2
3
4
const sum = (a,b) => {
    return a+b
}
sum(1,3) // 4
  • this:没有自己的 this,this指向外层作用域的最近的 this
  • 简写:
    1. 有且仅有一个参数,可省略 ()
    2. 函数体仅有一条语句,且是 return 语句,可省略 return 和 {}

高阶函数 & 链式编程(ES5 开始支持)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 需求:求数组 arr 中,所有小于 10 的正整数元素,乘以 2 的和
const arr = [ 1, -3 5 , 10,  6, 9, 32]
const result1 = arr.filter(item => item > 0 && item < 10) // [1,5,6,9]
const result2 = result1.map(item => item * 2 ) //[2,10,12,18]
const result3 = result2.reduce((preReturn, currentItem) => {
    const result = preReturn + currentItem
    return result
}, initialValue = 0) // 42
// 注意:initialValue 参数可选,忽略时reduce第一次遍历只执行 return 语句

// 链式编程
const result = arr.filter(item => item > 0 && item < 10)
.map(item => item * 2 )
.reduce((preReturn, currentItem) => {
    const result = preReturn + currentItem
    return result
}, initialValue = 0) // result = 42

Symbol 类型

ES6 新增的一个类型,表示一个独一无二的类型,无法和其他类型做运算,常用于给对象添加一个唯一的属性,防止重复

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 简单创建
const s1 = Symbol('s1') // 参数为描述字符串,可省略
const s2 = Symbol('s1') // s1 != s2
// Symbol.for() 创建
const s3 = Symbol.for('s3')
const s4 = Symbol.for('s3') // s3 = s4

// 用法:给对象添加唯一属性
const person = {
    name: 'Lisa',
    age: 20
}
const age = Symbol('age')
person[age] = 21
// person = { name: 'Lisa', age: 20, [Symbol('age')]: 21}

yield 与 函数生成器异步编程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* 获取 用户, 订单, 商品 数据 */
let iterator;
// 异步获取用户
const getUser = () => {
	const timer = setTimeout(() => {
        const data = '用户数据'
        iterator.next(data)
        clearTimeout(timer)
    }, 1000)
}
// 异步获取订单
const getOrder = () => {
	const timer = setTimeout(() => {
        const data = '订单数据'
        iterator.next(data)
        clearTimeout(timer)
    }, 1000)
}
// 异步获取商品
const getGood = () => {
	const timer = setTimeout(() => {
        const data = '商品数据'
        iterator.next(data)
        clearTimeout(timer)
    }, 1000)
}

// 定义函数生成器
function * gen() {
  const user = yield getUser() // 执行后挂起,迭代器 next() 后执行后面
  const order = yield getOrder() // 执行后挂起,迭代器 next() 后执行后面
  const good = yield getGood() // 执行后挂起,迭代器 next() 后执行后面
}

// 第一次调用
iterator = gen()
iterator.next()

Promise 异步编程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 实例化 Promise 对象
const response = new Promise( (resolve, reject) => {
    // 模拟请求
    const timer = setTimeout(() => {
        const data = '获取数据'
        const err = '发生错误'
        if(data) {
        	resolve(data)
    	} else {
            reject(err)
        }
    }, 1000)
})
response.then( data => {console.log('成功')}, err => {console.log('失败')})
		.catch( e => { console.log(e)})

Set & Map

  • Set:无重复的集合
1
2
3
4
5
6
7
const arr = [1,2,3,4,5,5]
const set = new Set(arr) // set = [1,2,3,4,5]
const newArr = [...new Set(arr)] // 数组去重
// 常用方法
set.hasItem(1) // 判断是否含有某个值
set.add(6) // 添加
set.delete(1) // 删除
  • Map:键值对集合,key 的类型可以是字符串或者 object
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const map = new Map()
map.set('name', 'Lisa') // 添加
const parents = {
    father: 'Bob',
    mother: 'Jure'
}
map.set(parents, true) // 添加
map.delete(parents) // 删除

// 遍历
for(let item of map) {
    // item 是个数组,第一个元素是key,第二个元素是value
    console.log(item[0])
    console.log(item[1])
}

模块化

  • export
1
2
3
4
5
6
7
8
9
export arr = [] // 单独暴露
export obj = {} // 单独暴露
export default { // 默认暴露
    name 'cat'
}

const a= 'hello'
const b = []
export { a, b} // 统一暴露
  • import
1
2
3
4
5
6
7
8
9
/**
* defaultObj 默认导入,可任意命名
* arr,a,b 普通导入
* obj as newObj 别名导入
*/
import defaultObj, {arr, obj as newObj, a, b} from '文件路径或模块';

/* 全部导入 */
import * as all from '文件路径或模块';

ES2016(开始以年份命名)

幂运算

1
2
3
console.log(2**4) // 16
let x = 5
x **= 2 // 25

Array.prototype.includes

1
2
3
const fruits = ["Banana", "Orange", "Apple", "Mango"];

fruits.includes("Mango"); // 为 true

ES2017

async & await

Promise 的语法糖

async:定义函数的时候,被声明的函数返回值会包装成一个 Promise 对象

await:调用函数的时候声明,将函数挂起,返回成功的 resolve。被声明函数是一个 Promise 的对象才有效

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 返回值不是一个 Promise 对象:
*	- 包装成 resolve 的对象
* 	- 抛出了异常,包装成 reject 的对象
* 返回值是 Promise
*/
const getName = async () => {
	return 'Lisa'   
}

const response = new Promise((resolve, reject) => {
    resolve('成功!')
    // reject('失败!')
})

const main = async () => {
    try {
        const result = await p
   	 	console.log(result) // 成功!
    } catch(e) {
        console.log(e) // 失败!
    }
}

Object 方法扩展

1
2
3
4
5
6
7
8
const obj = {
    name: 'Lisa',
    age: 18,
    gender: 'female'
}
const keys = Object.keys(obj) // ['name', 'age', 'gender']
const entries = Object.entries(obj) // 对象 =》 二维数组
// [['anem', 'Lisa'],['age', 18],['gender', 'female']]

ES2018

对象解构

解构赋值 ,这种写法同时支持对象类型了

正则扩展

命名捕获

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const str = '<a href="http://www.baidu.com">百度</a>'
const reg = /<a href="(.*)">(.*)<\/a>/
const result = reg.exec(str)
console.log(result[0]) // <a href="http://www.baidu.com">百度</a>
console.log(result[1]) // http://www.baidu.com
console.log(result[2]) // 百度
console.log(result.groups) // undefined

// 命名捕获
const reg2 = /<a href="(?<url>.*)">(?<text>.*)<\/a>/
const result2 = reg2.exec(str)
console.log(result.groups) // {url: 'http://www.baidu.com', text: '百度'}

反向断言

1
2
3
4
5
6
7
8
const str = 'ES56321你知道么555啦啦啦'
/* 正向断言 */
const reg = /\d+(?=啦)/
const result = reg.exec(str) // result[0] = 555

/* 反向断言 */
const reg2 = /(?<=么)\d+/
const result2 = reg2.exec(str) // result[0] = 555

贪婪匹配

1
2
// 默认 . 不能匹配换行
const reg = /.*?/gs // 能匹配换行

ES2019

Object.fromEntries

将Map或二维数组 => 对象

1
2
3
4
const map = new Map()
map.set('name', 'Lisa')
map.set('age', 18)
const obj = Object.fromEntries(map) // { name: 'Lisa', age: 18}

字符串扩展-trimStart & trimEnd

清除字符串左侧或者右侧空格

1
2
'   aaaa'.trimStart() // aaaa
'aaaa   '.trimEnd() // aaaa

数组扩展-数组降维

1
2
3
4
5
6
// Array.flat(int), int 默认为1
const arr = [11,12,13,[24,25,[36,37]]]
const result1 = arr.flat() // [11,12,13,24,25,36,37]
const result2 = arr.flat(2) // [11,12,13,24,25,[36,37]]

// Array.flatMap(),等同于map,如果 return [],降维

Symbol 扩展-获取描述

1
2
const s = Symbol('hello')
console.log(s.descriptiom) // hello

ES2020

Promise 扩展-Promise.allSettled()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const p1 = new Promise((resolve, reject) => {
    resolve('成功')
})
const p2 = new Promise((resolve, reject) => {
    reject('失败')
})
// 返回结果为一个 resolve 的 Promise[]
const res = Promise.allSettled([p1,p2]) 

// 返回结果为一个 resolve || reject 的 Promise[]
// 当全部 resolve 时,res2才为 resolve
const res2 = Promise.all([p1,p2])

字符串扩展-批量提取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const str = `
	<ul>
		<li>
			<a>肖申克的的救赎</a>
			<p>上映日期:1994-09-10</p>
		</li>
		<li>
			<a>阿甘正传</a>
			<p>上映日期:1994-07-10</p>
		</li>
	</ul>
`
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs
const result = str.matchAll(reg)
const arr = [...result]
// [[], []]

可选链操作符-?.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const myConfig = {
    db: {
        type: 'mysql',
    	host: '127.0.0.1',
        params: {
            username: 'root',
            password: 'root',
        },
    }
}

const addConfig = config => {
    const username = config.db.params.username
    console.log(username)
}
addConfig() // 出错 can't read property of undefined(read 'db') 

/* 以下写法可避免这种错误 */
const addConfig2 = config => {
    const username = config?.db?.params?.username
    console.log(username)
}
addConfig2() // 不出错, 打印 undefined
addConfig2(myConfig) // 打印 root

动态 import

1
2
3
4
5
6
7
const btn = document.getElementById('btn')

btn.onclick = () => {
    import('./hello.js').then(module => {
        
    })
}

BigInt 类型

1
2
3
4
5
6
7
const maxInt = number.MAX_SAFE_INTEGER // 9007199254740991
maxInt += 1 // 9007199254740992
maxInt += 1 // 9007199254740992 无法再加

// 大整型:用于运算,只能和大整型运算
const n = 1n // 定义一个大整型
BigInt(maxInt) + n // 9007199254740993n

全局 this-globalThis

浏览器端:指向 window

Node端:指向 global

ES2021

String.prototype.replaceAll()

1
2
3
4
5
// 之前
'jxvxscript'.replace(/x/g, 'a');

// 现在
'jxvxscript'.replaceAll('x', 'a');

Promise.any

只要其中的一个成功,就返回那个已经成功的 Promise

所有的都失败/拒绝 就返回一个失败的 Promise

1
2
3
4
5
6
7
8
9
const promise1 = new Promise((resolve, reject) => reject('我是失败的Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失败的Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList).then(values=>{  
  console.log(values);
})
.catch(e=>{  
  console.log(e);
});

运算符扩展

&&=

1
2
3
4
5
a = 1;
b = 2;
a &&= b // a=2
// 等价于
a && a = b

??=

1
2
3
4
5
a = 1;
b = 2;
a ??= b // a=1
// 等价于
a ?? a = b

数字分隔符

number 类型的数据,支持 _ 分割

1
2
3
const num = 100_220_330 
//  等价于
const num = 100220330

ES2022

Class Field

属性声明

class 定义属性可以不写 constructor

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 2022 前
class X {
  constructor() {
    this.a = 123;
  }
}
// 现在
class X {
  a = 123;
}

属性初始化

如果 class 中属性未初始化,默认值为 undefined

公共属性的 get 和 set

公共字段都是通过 Object.defineProperty 创造的,不会触发 class 中定义的 get set

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class A {
  set x(value) { console.log(++value); }
  get x() { return 'x' }
}
class B extends A {
  x = 1;
}
const b = new B();
b.x; // 1 (并不会返回'x')
b.x = 2; // 控制台不会打印3

私有属性

# 开头的属性被认为是私有属性

1
2
3
4
5
6
class X {
   // private a = 123
  #a = 123;
}
const x = new X();
x.#a // Uncaught SyntaxError: Private field '#a' must be declared in an enclosing class

正则扩展-indices

执行 String.match()Regexp.exec() 方法时,如果正则表达式选项配置了 /d 修饰符,

  • 结果多返回一个 indices 属性,返回匹配字符串的起始位置末尾位置,左闭右开区间

匿名捕获

1
2
3
4
5
const re1 = /a+/d;

const s1 = "aaabbb";
const m1 = re1.exec(s1);
m1.indices[0] //[0, 3];

具名捕获

1
2
3
4
5
6
const re1 = /a+(?<B>b+)/d;

const s1 = "aaabbbccc";
const m1 = re1.exec(s1);
m1.indices[1] //[3, 6];
m1.indices.groups //{ B: [3, 6] };

文件顶层使用 await

之前使用 await 需要在 async 声明的函数里,现在可以直接在 .mjs 文件顶层使用

1
2
3
4
5
// awaiting.mjs
import { process } from "./some-module.mjs";
const dynamic = import(computedModuleSpecifier);
const data = fetch(url);
export const output = process((await dynamic).default, await data);
1
2
3
// usage.mjs
import { output } from "./awaiting.mjs";
export function outputPlusValue(value) { return output + value }

class 扩展-in操作符

由于 class 私有属性/方法不可访问,只能使用 try catch 判断 class 定义对象的实例是否含有某个私有属性

现在可以使用 in 关键字来直接判断对象中是否存在某个私有字段、私有方法或者getter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class C {
  #brand;

  #method() {}

  get #getter() {}

  static isC(obj) {
    return #brand in obj && #method in obj && #getter in obj;
  }
}
const c = new C();
const d = {};
C.isC(c) // true
C.isC(d) // false

.at()

新增的取值方法,可作用于Array, String, TypedArray,接受一个参数

  • 参数为正整数n,返回第n个值
  • 参数为负整数n,返回第倒数n个值
  • 参数为其他值,返回第1个值
1
2
3
4
5
6
const arr = [1, 2, 3, 4, 5];
arr.at(1) // 2
arr.at(-1) // 5
arr.at(-10) // undefined
arr.at('aaaa') // 1
arr.at() // 1

Object.hasOwn

改良版 Object.hasOwnProperty

当使用Object.create(null) 创建一个没有继承 Object.prototype 的对象时,hasOwnProperty 方法无法直接使用:

1
2
3
4
const object = Object.create(null)
object.hasOwnProperty("foo") // Uncaught TypeError: Object.create(...).hasOwnProperty is not a function
Object.prototype.hasOwnProperty.call(object, 'foo') // false
Object.hasOwn(object, 'foo') // false

class 静态代码块

初始化 class 静态属性,可以开辟一个静态代码块,去初始化静态属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class C {
  static x = 3;
  static y;
  static z;
  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = 0;
      this.z = 0;
    }
  }
}

Error扩展-错误原因

Error 构造函数新增了一个可选参数 cause,允许我们在实例化 Error 时,将错误原因以参数形式传入,省去了我们自己单独处理的成本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
async function doJob() {
  const rawResource = await fetch('//domain/resource-a')
    .catch(err => {
      throw new Error('Download raw resource failed', { cause: err });
    });
  const jobResult = doComputationalHeavyJob(rawResource);
  await fetch('//domain/upload', { method: 'POST', body: jobResult })
    .catch(err => {
      throw new Error('Upload job result failed', { cause: err });
    });
}

ES2023

敬请期待……