ES6+新增与修改归纳(2015-2018)

文章按年份事件顺序进行统计,主要参考资料:

  1. ECMAScript 6 compatibility table (kangax.github.io)
  2. MDN Web Docs (mozilla.org)

最新浏览器主版本,不含更新版本

  • Chrome:104:2022-08-02
  • Edge:104:2022-08-05
  • Firefox:103:2022-07-26
  • Safari:15.6:2022-07-20
  • Opera:[Desktop] 89:2022-07-07;[Mobile] 69:2022-05-12

Chrome/Edge/Opera 为 Blink 内核;Firefox 为 Gecko 内核;Safari/IOS 为 WebKit 内核
本文目前主要讨论PC浏览器兼容

一:ES2015

鉴于ES6新增属性太多,这里只归纳几个

1.1:尾调用优化(Tail Call Optimization/Proper Tail Calls)

ChromeEdgeFirefoxSafariOperaOpera Mobile
10+
2016-09

这个伴随ES6就被谈及很多的一个优化;不过很可惜,到目前为止仍只有 Safari 一家实现了该优化,其他几家在加入该优化过程中遇到各种问题,甚至之后很长一段时间都不会有什么积极的更新了;相关故事可以读读:

1.2:二进制数/八进制数(Binary Integer Literal/Octal Integer Literal

ChromeEdgeFirefoxSafariOperaOpera Mobile
41+12+25+9+28+
2016-052016-072013-092015-102015-05

二进制数:以 0b/0B开头,后面接数字0-1
八进制数:以 0o/0O开头,后面接数字0-7;ES5非严格模式支持前缀0的八进制表示法,ES6相当于规范这几种表示法,和十六进制表示法一样
十六进制:以 0x/0X 开头,后面接数字0-9/a-f/A-F

// 二进制
0b1 === 1 && 0B1 === 1;
0b10 === 2 && 0B10 === 2;
Number(0b1) === 1 && Number(0B10) === 2;
// 八进制
0o1 === 1 && 0O1 === 1;
0o10 === 8 && 0O10 === 8;
Number(0o1) === 1 && Number(0o10) === 8;
// 十六进制
0x1 === 1 && 0X1 === 1;
0x10 === 16 && 0X10 === 16;
Number(0x1) === 1 && Number(0X10) === 16;

1.3:正则修饰符:y、u

ChromeEdgeFirefoxSafariOperaOpera Mobile
49+/50+13+3+/46+10+36+/37+36+/37+
16-03/16-042015-1108-06/16-042016-0916-03/16-0516-03/16-06

ysticky 粘性标志,下一次匹配一定在 lastIndex 位置开始;RegExp.prototype.sticky 只读属性判断是否启用 y 标志
uunicode 相关特性;RegExp.prototype.unicode 只读属性判断是否启用 u 标志

const text = '123 456';
const regY = /\d/y;
const regG = /\d/g;
let result;
// 打印3次,在匹配完‘3’之后,lastIndex 为3,\s(空格)未匹配成功,直接结束
while (result = regY.exec("123 456")) {
    console.log(result, regY.lastIndex);
}
// 打印6次,lastIndex 为 3 时未匹配成功,则 lastIndex + 1 继续匹配,直到全部匹配完
while (result = regG.exec("123 456")) {
    console.log(result, regG.lastIndex);
}

1.4:Reflect

ChromeEdgeFirefoxSafariOperaOpera Mobile
49+12+42+10+36+36+
2016-032015-072015-112016-092016-032016-03

Reflect 对象是一个内置对象,提供拦截JavaScript操作的方法;其所有属性和方法都是静态的

静态方法参数都有类型要求,类型不匹配会报类型错误TypeError;与之相类似的Object方法则会进行强制转换,部分方法在ES5会报错,在ES6被修改为强制转换:Object.keysObject.getOwnPropertyNamesObject.assign

Reflect.apply(); // Function.prototype.apply() 
Reflect.construct(); // new 构造函数
Reflect.has(); // name in Object
Reflect.getPrototypeOf(); // Object.getPrototypeOf()
                          // ES6之前不会进行强制转换,之后会
Reflect.isExtensible(); // Object.isExtensible()

// @return {Boolean} 操作是否成功
Reflect.defineProperty(); // Object.defineProperty() 返回对象
Reflect.deleteProperty(); // delete Objet[name] 返回 true
                          // 除非 configurable=false(非严格模式)
Reflect.get(); // Objet[name]
Reflect.set(); // Objet[name] = value
Reflect.preventExtensions(); // Object.preventExtensions() 返回对象
Reflect.setPrototypeOf(); // Object.setPrototypeOf() 返回对象

// @return {Object|undefined} 属性描述符
Reflect.getOwnPropertyDescriptor(); // Objet.getOwnPropertyDescriptor()

// @return {(Symbol|String)[]} 属性名列表,包含不可枚举属性、Symbol属性
Reflect.ownKeys(); // Object.keys 不含不可枚举、Symbol
                   // Object.getOwnPropertyNames 含不可枚举
                   // Object.getOwnPropertySymbols 含不可枚举

1.5:内置Symbol(Well-known Symbols)

ChromeEdgeFirefoxSafariOperaOpera Mobile
73+79+78+14+60+52+
2019-032020-012020-062020-092019-042019-05
因为每个属性值被添加到浏览器的时间不同,此处浏览器版本为最近一次添加属性的版本

内置Symbol,通常被用作对象或类的属性键,对应的值用作规范算法的扩展、或者重构内部语言行为;该重构行为改仅针对修改的对象或类生效,不会修改全局的默认方法

// 迭代器:Symbol.iterator
const arr = [1,2,3];
arr[Symbol.iterator] = function* (...params) {
    for (let i=0;i<arr.length;i++) {
        yield 2*arr[i];
    }
}
console.log(arr); // Array [1, 2, 3]
console.log([...arr]); // Array [2, 4, 6]

// 字符串替换、拆分:Symbol.replace、Symbol.split
const obj = {
    [Symbol.replace]: (origin, replaceTo)=> {
        return `__${origin}_${replaceTo}__`;
    },
    [Symbol.split]: (origin, limit)=> {
        return `${Array(2).fill(origin)}`;
    },
}
const str = 'test';
console.log(str.replace(obj, 'to')); // "__test_to__"
console.log(str.split(obj, 5)); // "test,test"

1.6:Array.prototype.splice

ChromeEdgeFirefoxSafariOperaOpera Mobile
1+12+1+1+4+10.1+
2008-122015-072004-112003-062000-062010-11

这确实是ES6新标准,但是各大浏览器其实早就加入了该实现方法,新标准只是给他一个名分而已

1.7:Number.EPSILON

ChromeEdgeFirefoxSafariOperaOpera Mobile
34+12+25+9+21+21+
2014-042015-072013-102015-092014-052014-04

表示 1 与 Number 类型可表示的大于 1 的最小的浮点数之间的差值,可理解为:最小浮点数


可以用来判断浮点数之间的计算值比较,差值 < Number.EPSILON 即认定为相等:

const x = 0.1, y = 0.2, z = 0.3;
const isEqual = (Math.abs(x + y - z) < Number.EPSILON);

二:ES2016

2.1:幂运算(**/**=)

ChromeEdgeFirefoxSafariOperaOpera Mobile
52+14+52+10.1+39+41+
2016-072016-082017-032017-032016-082016-10

求幂,等效于Math.pow,也接受BigInts作为参数

2.2:Array.prototype.includes

ChromeEdgeFirefoxSafariOperaOpera Mobile
47+14+43+9+34+34+
2015-122016-082015-122015-092015-122015-12

arr.includes(valueToFind[, fromIndex])接受两个参数;

第一个参数不传,则默认为undefined
第二个参数为查找开始的索引值,为可选参数:

  • fromIndex >= 0 && fromIndex <= arr.length - 1:正常查找
  • fromIndex > arr.length - 1:返回false
  • fromIndex < 0:计算fromIndex = Math.max(0, fromIndex + arr.length)之后再判断

includes() 有意设计为通用方法,可以被用于其他类型(如类数组对象)的对象,包括querySelectorAllObject.keysmap.keys

[].includes(); // false
[undefined].includes(); // true
[1,].includes(); // false
[1,,].includes(); // true
Array(2).includes(); // true

// 其他类型
[].includes.call(1, 1); // 数字,false
[].includes.call(true, true); // 布尔,false
[].includes.call(Symbol(1), 1); // Symbol,false
[].includes.call(BigInt(1), 1); // BigInt,false
[].includes.call(()=>{}, 1); // Function,false
[].includes.call(/\d/, 1); // RegExp,false
[].includes.call(null, null); // null,TypeError
[].includes.call(undefined, undefined); // undefined,TypeError

三:ES2017

3.1:对象取值(values/entries)

ChromeEdgeFirefoxSafariOperaOpera Mobile
54+14+47+10.1+41+41+
2016-102016-082016-062017-032016-102016-10

Object.values对象自身的所有可枚举属性值的数组;顺序与使用for...in循环的顺序相同
Object.entries对象自身可枚举属性的键值对数组;顺序与使用for...in循环的顺序相同

Object.keys方法在浏览器中很早就实现了,直到ES2015成为标准;然后时隔两年,Object.values才成为新标准,浏览器也是跟随标准才实现该方法;所以对于一些较老的浏览器来说,很可能实现了Object.keys而没有实现Object.values/Object.entries

Object.entries最常用的作用估计就是转换成map了

const obj = { foo: "bar", baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map) // Map(2) {'foo' => 'bar', 'baz' => 42}

3.2:属性描述符(getOwnPropertyDescriptors)

ChromeEdgeFirefoxSafariOperaOpera Mobile
54+14+47+10.1+41+41+
2016-102016-082016-062017-032016-102016-10

对象自身所有属性的描述符,没有任何自身属性,则返回空对象
主要用途:

  1. 浅拷贝

实现拷贝对象时,也拷贝它的原型、属性特性

Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj)
);
  1. 创建子类
// 传统写法的另一种实现
function superclass() {}
superclass.prototype = {
    // 在这里定义方法和属性
};
function subclass() {}
subclass.prototype = Object.create(superclass.prototype, Object.getOwnPropertyDescriptors({
    // 在这里定义方法和属性
}));

3.3:字符串填充(padStart/padEnd)

ChromeEdgeFirefoxSafariOperaOpera Mobile
57+15+48+10+44+43+
2017-032017-042016-082016-092017-032017-09
  • str.padStart(targetLength [, padString])
  • str.padEnd(targetLength [, padString])

targetLength为目标长度,只有大于字符本身长度时,才会执行填充,小于时是直接返回原字符
padString为填充字符串,长度够时反复填充,长度不够时优先保留左侧字符

3.4:函数尾逗号(trailing/final commas)

ChromeEdgeFirefoxSafariOperaOpera Mobile
58+14+52+10+45+43+
2017-042016-082017-032016-092017-052017-09

针对函数参数中的尾逗号,在函数定义、函数调用中,最后一个参数后面可追加逗号:,
在没有参数、结构参数时,不能添加尾逗号:

// 合法的写法
function f(p,) {}
(p,) => {};
f(p,);
Math.max(10, 20,);

// 不合法的写法
function f(,) {} // SyntaxError: missing formal parameter
f(,);            // SyntaxError: expected expression, got ','
(...p,) => {}    // SyntaxError: expected closing parenthesis, got ','

3.5:异步函数(async/await)

ChromeEdgeFirefoxSafariOperaOpera Mobile
55+15+52+10+42+42+
2016-122017-042017-032016-092016-122017-01

asyncawait关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise

3.6:共享内存(SharedArrayBuffer/Atomics)

SharedArrayBuffer

ChromeEdgeFirefoxSafariOperaOpera Mobile
68+79+78+10+55+63+
2018-072020-012020-062016-092018-082021-04
为应对幽灵漏洞,所有主流浏览器均默认于 2018 年 1 月 5 日禁用 SharedArrayBuffer;
在 2020 年,一种新的、安全的方法已经标准化,以重新启用 SharedArrayBuffer

Atomics

ChromeEdgeFirefoxSafariOperaOpera Mobile
87+87+79+①②15.2+75+63+
2020-112020-112020-072021-122021-032021-04
①:仅支持SharedArrayBuffer对象操作;②:不支持waitAsync方法

SharedArrayBuffer对象用来表示一个通用的、固定长度的原始二进制数据缓冲区,类似于ArrayBuffer对象,它们都可以用来在共享内存(shared memory)上创建视图
Atomics对象提供了一组静态方法对SharedArrayBufferArrayBuffer对象进行原子操作

四:ES2018

4.1:对象的剩余/展开属性(Object Rest/Spread Properties)

ChromeEdgeFirefoxSafariOperaOpera Mobile
60+79+55+11.1+47+44+
2017-072020-012017-082018-042017-082017-12

属性拓展符在对象中的拓展;在某些浏览器版本中即使支持拓展符...,但不一定支持在对象中使用

可用来实现对象的浅拷贝和合并,与Object.assign()作用类似,但Object.assign()函数会触发 setters,而展开语法则不会

// 剩余属性
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }
// 展开属性
let n = { x, y, ...z };
n; // { x: 1, y: 2, a: 3, b: 4 }

4.2:Promise.prototype.finally

ChromeEdgeFirefoxSafariOperaOpera Mobile
63+18+58+11.1+50+45+
2017-122018-102018-012018-042018-012018-05

promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数

此方法是新增的方法,和Promise.prototype.thenPromise.prototype.catch拥有不同的兼容性表现,某些浏览器版本中存在兼容性问题

4.3:正则修饰符:s

ChromeEdgeFirefoxSafariOperaOpera Mobile
62+79+78+11.1+49+46+
2017-102020-012020-062018-042017-112018-05

启用dotAll模式,特殊字符.可匹配行终结符(换行符),相当于匹配“任意单个字符”:

  • U+000A 换行符(\n
  • U+000D 回车符(\r
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)

RegExp.prototype.dotAll只读属性用来判断是否启用了s标志

4.4:命名分组捕获(Named capture groups)

ChromeEdgeFirefoxSafariOperaOpera Mobile
64+79+78+11.1+51+47+
2018-012020-012020-062018-042018-022018-07

以前的分组捕获都是用索引去取值,用result[0]result[2]的形式取值,很多场景都分不清它们是什么含义,于是就有了命名捕获分组;这真是贴心的更新,不过需要考虑老浏览器兼容性

  • 命名((?<name>...))与取值(属性groups
// 获取年月日的匹配
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// 以前的取值
let date = result[0];   // '2015-01-02'
let year = result[1];   // '2015'
let month = result[2];  // '01'
let day = result[3];    // '02'
// 命名取值
let date = result[0];             // '2015-01-02'
let year = result.groups.year;    // '2015'
let month = result.groups.month;  // '01'
let day = result.groups.day;      // '02'
// 也可以直接结构取值
let {groups: {year, month, day}} = re.exec('2015-01-02');
  • 命名引用(\k<name>
// (?<half>.*):匹配任意字符,命名为half
// \k<half>:引用命名为half的匹配,即与前面是相同匹配
// 匹配结果就是 ABA 的字符串模式
let duplicate = /^(?<half>.*).\k<half>$/;
duplicate.test('a*b');    // false
duplicate.test('a_a');    // true
duplicate.test('ab-ab');  // true
duplicate.test('aaaa');   // false
duplicate.test('aaaaa');  // true
  • String.prototype.replace中的使用
// 把日期格式 yyyy-mm-dd 替换为 dd/mm/yyy
let str = '2015-02-01';
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
// 以往的写法
let result = str.replace(re, '$3/$2/$1'); // '01/02/2015'
// 命名分组写法
let result = str.replace(re, '$<day>/$<month>/$<year>'); // '01/02/2015'

4.5:后行断言(Lookbehind assertion

ChromeEdgeFirefoxSafariOperaOpera Mobile
62+79+78+49+46+
2017-102020-012020-062017-112018-05

(?<=y)x仅当’x’前面是’y’时,匹配’x’,这种叫做后行断言
(?<!y)x仅当’x’前面不是’y’时,匹配’x’,这被称为反向否定查找

let str1 = 'JackSprat';
let str2 = 'TomSprat';
let re1 = /(?<=Jack)Sprat/;  // 匹配 JackSprat 中的 Sprat
let re2 = /(?<!=Jack)Sprat/; // 匹配 不是 JackSprat 中的 Sprat
re1.exec(str1); // ['Sprat', index: 4, input: 'JackSprat', groups: undefined]
re1.exec(str2); // null
re2.exec(str1); // ['Sprat', index: 4, input: 'JackSprat', groups: undefined]
re2.exec(str2); // ['Sprat', index: 3, input: 'TomSprat', groups: undefined]

4.6:Unicode转义捕获(Unicode Property Escapes)

ChromeEdgeFirefoxSafariOperaOpera Mobile
64+79+78+11.1+51+47+
2018-012020-012020-062018-042018-022018-07

根据Unicode属性,用来匹配表情、标点符号、字母、甚至特定语言或文字等。同一符号可以拥有多种Unicode属性,属性则有binary(“boolean-like“)和non-binary之分
使用时必须依靠\u标识,使用转义字符\p{...}\P{...}

// Non-binary 属性
\p{Unicode 属性值}
\p{Unicode 属性名=Unicode 属性值}
// Binary and non-binary 属性
\p{UnicodeBinary 属性名}
// \P 为 \p 取反
\P{Unicode 属性值}
\P{UnicodeBinary 属性名}

let sentence = 'A ticket to 大阪 costs ¥2000 👌.';
let regexpEmojiPresentation = /\p{Emoji_Presentation}/gu;
sentence.match(regexpEmojiPresentation);  // ["👌"]

let regexpNonLatin = /\P{Script_Extensions=Latin}+/gu;
sentence.match(regexpNonLatin);  // [" ", " ", " 大阪 ", " ¥2000 👌."]

let regexpCurrencyOrPunctuation = /\p{Sc}|\p{P}/gu;
sentence.match(regexpCurrencyOrPunctuation);  // ["¥", "."]

4.7:Function.prototype.toString

ChromeEdgeFirefoxSafariOperaOpera Mobile
66+79+54+53+47+
2018-042020-012017-062018-052018-07

新规范:要求Function.prototype.toString的返回值与声明的源代码完全现统,包括空格和注释;或者因某种原因,主机没有源代码,则要求返回一个原生函数字符串