JavaScript-ES7~ES11新特性

在JavaScript的发展历程中,版本历史最悠久的就是ES5,ES5是现在JavaScript的基础,但是因为有很多当初设计上的缺陷,比如var的变量提升,比如异步处理问题,比如this指向的问题,比如没有类的概念等等。

到了ES6的时候解决了大部分的问题,比如引入了letconst声明变量和常量。引入了Promise解决异步问题,引入了()=>解决this指向的问题,引入了class关键字解决没有类的概念的问题。

JavaScript版本发布进程:

版本 发表日期 与前版本的差异
1 1997年6月 首版
2 1998年6月 格式修正,以使得其形式与ISO/IEC16262国际标准一致
3 1999年12月 强大的正则表达式,更好的词法作用域链处理,新的控制指令,异常处理,错误定义更加明确,数据输出的格式化及其它改变
4 放弃 由于关于语言的复杂性出现分歧,第4版本被放弃,其中的部分成为了第5版本及Harmony的基础
5 2009年12月 新增“严格模式(strict mode)”,一个子集用作提供更彻底的错误检查,以避免结构出错。澄清了许多第3版本的模糊规范,并适应了与规范不一致的真实世界实现的行为。增加了部分新功能,如getters及setters,支持JSON以及在对象属性上更完整的反射
5.1 2011年6月 ECMAScript标5.1版形式上完全一致于国际标准ISO/IEC 16262:2011。
6 2015年6月 ECMAScript 2015(ES2015),第 6 版,最早被称作是 ECMAScript 6(ES6),添加了类和模块的语法,其他特性包括迭代器,Python风格的生成器和生成器表达式,箭头函数,二进制数据,静态类型数组,集合(maps,sets 和 weak maps),promise,reflection 和 proxies。作为最早的 ECMAScript Harmony 版本,也被叫做ES6 Harmony。
7 2016年6月 ECMAScript 2016(ES2016),第 7 版,多个新的概念和语言特性
8 2017年6月 ECMAScript 2017(ES2017),第 8 版,多个新的概念和语言特性
9 2018年6月 ECMAScript 2018 (ES2018),第 9 版,包含了异步循环,生成器,新的正则表达式特性和 rest/spread 语法。
10 2019年6月 ECMAScript 2019 (ES2019),第 10 版
11 2020年6月 ECMAScript 2020 (ES2020),第 11 版

1. ES7

1.1 Array.prototype.includes()

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。

const array1 = [1, 2, 3];

console.log(array1.includes(2));
// expected output: true

const pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat'));
// expected output: true

console.log(pets.includes('at'));
// expected output: false

MDN文档

1.2 指数操作符(**)

Math.pow(..)具有同样的效果。

console.log(2**10); // 输出1024,即2的10次方。

2. ES8

2.1 async await

MDN文档 async

MDN文档 await

async和await可以像编写同步代码一样编写异步代码。

随着Promise的大规模使用,当.then()过多后,就也会出现类似于回调地狱的情况,这个时候使用async和await就可以使代码更加简洁。

(async () => {
  let time = await a1();
  time = await a2(time);
  await a3(time);
  b1();
  b2();
  b3();
})();

注意:await一定要在async中使用。

2.2 Object.values()

Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组,值的顺序与使用for...in循环的顺序相同 ( 区别在于 for-in 循环枚举原型链中的属性 )。

MDN文档

2.3 Object.entries()

Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。

MDN文档

2.4 Object.getOwnPropertyDescriptors()

返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

MDN文档

2.5 SharedArrayBuffer对象

用来表示一个通用的,固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被分离。

MDN文档

2.6 Atomics对象

提供了一组静态方法对 SharedArrayBufferArrayBuffer 对象进行原子操作。

MDN文档

2.7 String padding

padStart() 方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。

MDN文档

padEnd() 方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。

MDN文档

3. ES9

3.1 对象展开

在以前,...只能用来展开数组,但是现在可以用来展开对象了。

let a = {
  name: "张三",
  sex: "男"
};
let b = {
  a: "李四",
  b: "男"
};

let c = {
  ...b,
  ...a
};
console.log(c);

注意:属性名相同后面的属性名会覆盖前面的属性名。

3.2 Promise.prototype.finally()

在Promise中,如果有不管成功还是失败都需要执行的语句,那么就可以用Promise.prototype.finally()。

MDN文档

3.3 正则表达式

3.3.1 命名捕获组

ES2018允许命名捕获组使用符号?<name>,在打开捕获括号(后立即命名,示例如下:

const str =
  `<p>百度</p>
    <p>www.baidu.com</p>
  `;
let reg = /<p>(?<name>.*)<\/p>\s*<p>(?<url>.*)<\/p>/g;
const result = reg.exec(str);
console.log(result.groups.name); // 百度
console.log(result.groups.url); // www.baidu.com

3.3.2 反向断言

(?<=表达式),指在某个位置向左看,表示所在位置左侧必须能匹配表达式

3.3.3 dotAll模式

正则表达式中点.匹配除回车外的任何单字符,标记s改变这种行为,即可以匹配任意字符。

4. ES10

4.1 Object.fromEntries()

Object.fromEntries() 方法把键值对列表转换为一个对象。

MDN文档

4.2 trimStart()和trimEnd()

trimStart() 方法从字符串的开头删除空格。trimLeft() 是此方法的别名。

trimEnd() 方法从一个字符串的末端移除空白字符。trimRight() 是这个方法的别名。

4.3 Array.prototype.flat()

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

const arr1 = [0, 1, 2, [3, 4]];

console.log(arr1.flat());
//  [0, 1, 2, 3, 4]

const arr2 = [0, 1, 2, [[[3, 4]]]];

console.log(arr2.flat(2));
//  [0, 1, 2, [3, 4]]

MDN文档

4.4 Array.prototype.flatMap()

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为1的 flat 几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。

var arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]); 
// [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]

arr1.flatMap(x => [[x * 2]]);
// [[2], [4], [6], [8]]

MDN文档

4.5 Function.prototype.toString()

toString() 方法返回一个表示当前函数源代码的字符串。

MDN文档

4.6 String.prototype.matchAll

matchAll() 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

MDN文档

4.7 Object.fromEntries()

Object.fromEntries() 方法把键值对列表转换为一个对象。

MDN文档

4.8 Symbol.prototype.description

description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

MDN文档

5. ES11

5.1 私有属性

用在类的声明中,在变量名称前面加上#

例如:

class Student {
  name;
  #age;
  #sex;

  constructor(name, age, sex) {
    this.name = name;
    this.#age = age;
    this.#sex = sex;
  }
}

let s1 = new Student("张三", 18, "男");
console.log(s1.#age); // 报错: Uncaught SyntaxError: Private field '#age' must be declared in an enclosing class

即在类的外部无法访问到该属性,但是在类的内部可以访问到该属性。

5.2 Promise.allSettled

该**Promise.allSettled()**方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result.status)));

// expected output:
// "fulfilled"
// "rejected"

MDN文档

5.3 String.prototype.matchAll

在正则匹配中一个非常实用的方法,**matchAll()** 方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]

MDN文档

5.4 import()

动态加载模块的功能其实早在Webpack中已经实现,即动态加载模块。

const btn = document.getElementById("btn");

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

可以看到,上面的代码在点击按钮后,才动态加载hello.js文件。

5.5 BigInt

在js中安全的整数在 -(2^53 - 1) 到 2^53 - 1 之间的数值(包含边界值)。

如果超出了这个范围,在计算时就无法得到正确的值。

例如:

let maxNumber = Number.MAX_SAFE_INTEGER;

console.log("maxNumber + 1 " + (maxNumber + 1));
console.log("maxNumber + 2 " + (maxNumber + 2));
console.log("maxNumber + 3 " + (maxNumber + 3));
console.log("maxNumber + 4 " + (maxNumber + 4));

// 输出结果
maxNumber + 1 9007199254740992
maxNumber + 2 9007199254740992
maxNumber + 3 9007199254740994
maxNumber + 4 9007199254740996

而使用BigInt就可以正确的表示:

let maxNumber = Number.MAX_SAFE_INTEGER;

console.log("maxNumber + 1 " + (BigInt(maxNumber) + BigInt(1)));
console.log("maxNumber + 2 " + (BigInt(maxNumber) + BigInt(2)));
console.log("maxNumber + 3 " + (BigInt(maxNumber) + BigInt(3)));
console.log("maxNumber + 4 " + (BigInt(maxNumber) + BigInt(4)));

maxNumber + 1 9007199254740992
maxNumber + 2 9007199254740993
maxNumber + 3 9007199254740994
maxNumber + 4 9007199254740995

除了使用BigInt()这种方式,还可以通过下面的方式声明:

let maxNumber = 12312312313123432n;

即在数字后面加n

注意:BigInt()不能直接和Number类型进行计算。会报错Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions。

5.6 globalThis

在浏览器环境中,globalThis无论在什么地方,都指向Window ,而在Node环境中,指向一个全局的Object对象。

5.7 可选链操作符(?.)

可选链操作符(Optional Chaining)通常在我们编写项目时,如果要通过ajax动态获取数据,但是并不知道后端返回来的数据是否是空值,所以我们需要进行很多判断。

const data = {
  name: "张三",
  age: 18,
  sex: "男",
  friend: {
    name: "李四"
  }
};

console.log(data.friend.name);

假设data是通过ajax获取的数据,data.friend.name存在,则可以正常输出,如果不存在:

const data = {
  name: "张三",
  age: 18,
  sex: "男"
};
console.log(data.friend.name); // 报错:Uncaught TypeError: Cannot read property 'name' of undefined

所以这个时候我们平时就要做很多判断。

const data = {
  name: "张三",
  age: 18,
  sex: "男"
};

console.log(data && data.friend && data.friend.name); // undefined

而有了可选链操作符(?.)后,我们可以直接简写成

const data = {
  name: "张三",
  age: 18,
  sex: "男"
};

console.log(data?.friend?.name);

同时,这里再次安利WebStorm,它可以自动重构代码:

UseOptionalChaining

5.8 空值合并运算符(??)

空值合并运算符(Nullish Coalescing Operator)。新的运算符??,和它功能类似的有||

在JavaScript中很多情况比如空字符串""0,等等情况,如果用在条件判断中,都会被认为是false。

例如:

if (0) {
  console.log("true");
} else {
  console.log("false");
}

最终的输出结果为false。

这就可能导致一个问题,就是在使用||运算符时,只需要在左边的值为undefined或者null的时候,才返回右边的值。

??运算符的出现就是为了解决这个问题,例如下面的情况:

let a = 0;
let b = a || "aaa";
let c = a ?? "aaa";
console.log("b的值是 " + b);
console.log("c的值是 " + c);

最终输出结果为:

b的值是 aaa
c的值是 0