ES6+新增与修改归纳(2015-2018)
文章按年份事件顺序进行统计,主要参考资料:
最新浏览器主版本,不含更新版本
- 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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| ❌ | ❌ | ❌ | 10+ | ❌ | ❌ |
| ➖ | ➖ | ➖ | 2016-09 | ➖ | ➖ |
这个伴随ES6就被谈及很多的一个优化;不过很可惜,到目前为止仍只有 Safari 一家实现了该优化,其他几家在加入该优化过程中遇到各种问题,甚至之后很长一段时间都不会有什么积极的更新了;相关故事可以读读:
- Tail Call Optimization implementation in Javascript Engines – Stack Overflow
- What happened to proper tail calls in JavaScript?
1.2:二进制数/八进制数(Binary Integer Literal/Octal Integer Literal)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 41+ | 12+ | 25+ | 9+ | 28+ | ✅ |
| 2016-05 | 2016-07 | 2013-09 | 2015-10 | 2015-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
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 49+/50+ | 13+ | 3+/46+ | 10+ | 36+/37+ | 36+/37+ |
| 16-03/16-04 | 2015-11 | 08-06/16-04 | 2016-09 | 16-03/16-05 | 16-03/16-06 |
y:sticky 粘性标志,下一次匹配一定在 lastIndex 位置开始;RegExp.prototype.sticky 只读属性判断是否启用 y 标志u:unicode 相关特性;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
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 49+ | 12+ | 42+ | 10+ | 36+ | 36+ |
| 2016-03 | 2015-07 | 2015-11 | 2016-09 | 2016-03 | 2016-03 |
Reflect 对象是一个内置对象,提供拦截JavaScript操作的方法;其所有属性和方法都是静态的
静态方法参数都有类型要求,类型不匹配会报类型错误TypeError;与之相类似的Object方法则会进行强制转换,部分方法在ES5会报错,在ES6被修改为强制转换:Object.keys、Object.getOwnPropertyNames、Object.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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 73+ | 79+ | 78+ | 14+ | 60+ | 52+ |
| 2019-03 | 2020-01 | 2020-06 | 2020-09 | 2019-04 | 2019-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"
- Symbol – JavaScript | MDN (mozilla.org)
- ECMAScript 2015 Language Specification – ECMA-262 6th Edition (ecma-international.org)
1.6:Array.prototype.splice
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 1+ | 12+ | 1+ | 1+ | 4+ | 10.1+ |
| 2008-12 | 2015-07 | 2004-11 | 2003-06 | 2000-06 | 2010-11 |
这确实是ES6新标准,但是各大浏览器其实早就加入了该实现方法,新标准只是给他一个名分而已
1.7:Number.EPSILON
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 34+ | 12+ | 25+ | 9+ | 21+ | 21+ |
| 2014-04 | 2015-07 | 2013-10 | 2015-09 | 2014-05 | 2014-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:幂运算(**/**=)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 52+ | 14+ | 52+ | 10.1+ | 39+ | 41+ |
| 2016-07 | 2016-08 | 2017-03 | 2017-03 | 2016-08 | 2016-10 |
求幂,等效于Math.pow,也接受BigInts作为参数
2.2:Array.prototype.includes
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 47+ | 14+ | 43+ | 9+ | 34+ | 34+ |
| 2015-12 | 2016-08 | 2015-12 | 2015-09 | 2015-12 | 2015-12 |
arr.includes(valueToFind[, fromIndex])接受两个参数;
第一个参数不传,则默认为undefined
第二个参数为查找开始的索引值,为可选参数:
fromIndex >= 0 && fromIndex <= arr.length - 1:正常查找fromIndex > arr.length - 1:返回falsefromIndex < 0:计算fromIndex = Math.max(0, fromIndex + arr.length)之后再判断
includes() 有意设计为通用方法,可以被用于其他类型(如类数组对象)的对象,包括querySelectorAll、Object.keys、map.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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 54+ | 14+ | 47+ | 10.1+ | 41+ | 41+ |
| 2016-10 | 2016-08 | 2016-06 | 2017-03 | 2016-10 | 2016-10 |
Object.对象自身的所有可枚举属性值的数组;顺序与使用valuesfor...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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 54+ | 14+ | 47+ | 10.1+ | 41+ | 41+ |
| 2016-10 | 2016-08 | 2016-06 | 2017-03 | 2016-10 | 2016-10 |
对象自身所有属性的描述符,没有任何自身属性,则返回空对象
主要用途:
- 浅拷贝
实现拷贝对象时,也拷贝它的原型、属性特性
Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
- 创建子类
// 传统写法的另一种实现
function superclass() {}
superclass.prototype = {
// 在这里定义方法和属性
};
function subclass() {}
subclass.prototype = Object.create(superclass.prototype, Object.getOwnPropertyDescriptors({
// 在这里定义方法和属性
}));
3.3:字符串填充(padStart/padEnd)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 57+ | 15+ | 48+ | 10+ | 44+ | 43+ |
| 2017-03 | 2017-04 | 2016-08 | 2016-09 | 2017-03 | 2017-09 |
str.padStart(targetLength [, padString])
str.padEnd(targetLength [, padString])
targetLength为目标长度,只有大于字符本身长度时,才会执行填充,小于时是直接返回原字符padString为填充字符串,长度够时反复填充,长度不够时优先保留左侧字符
- String.prototype.padStart() – JavaScript | MDN (mozilla.org)
- String.prototype.padEnd() – JavaScript | MDN (mozilla.org)
3.4:函数尾逗号(trailing/final commas)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 58+ | 14+ | 52+ | 10+ | 45+ | 43+ |
| 2017-04 | 2016-08 | 2017-03 | 2016-09 | 2017-05 | 2017-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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 55+ | 15+ | 52+ | 10+ | 42+ | 42+ |
| 2016-12 | 2017-04 | 2017-03 | 2016-09 | 2016-12 | 2017-01 |
async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise
3.6:共享内存(SharedArrayBuffer/Atomics)
SharedArrayBuffer
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 68+ | 79+ | 78+ | 10+ | 55+ | 63+ |
| 2018-07 | 2020-01 | 2020-06 | 2016-09 | 2018-08 | 2021-04 |
在 2020 年,一种新的、安全的方法已经标准化,以重新启用 SharedArrayBuffer
Atomics
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| ①87+ | ①87+ | ②79+ | ①②15.2+ | ①75+ | ①63+ |
| 2020-11 | 2020-11 | 2020-07 | 2021-12 | 2021-03 | 2021-04 |
SharedArrayBuffer对象操作;②:不支持waitAsync方法SharedArrayBuffer对象用来表示一个通用的、固定长度的原始二进制数据缓冲区,类似于ArrayBuffer对象,它们都可以用来在共享内存(shared memory)上创建视图Atomics对象提供了一组静态方法对SharedArrayBuffer和ArrayBuffer对象进行原子操作
四:ES2018
4.1:对象的剩余/展开属性(Object Rest/Spread Properties)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 60+ | 79+ | 55+ | 11.1+ | 47+ | 44+ |
| 2017-07 | 2020-01 | 2017-08 | 2018-04 | 2017-08 | 2017-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
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 63+ | 18+ | 58+ | 11.1+ | 50+ | 45+ |
| 2017-12 | 2018-10 | 2018-01 | 2018-04 | 2018-01 | 2018-05 |
在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数
此方法是新增的方法,和Promise.prototype.then、Promise.prototype.catch拥有不同的兼容性表现,某些浏览器版本中存在兼容性问题
4.3:正则修饰符:s
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 62+ | 79+ | 78+ | 11.1+ | 49+ | 46+ |
| 2017-10 | 2020-01 | 2020-06 | 2018-04 | 2017-11 | 2018-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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 64+ | 79+ | 78+ | 11.1+ | 51+ | 47+ |
| 2018-01 | 2020-01 | 2020-06 | 2018-04 | 2018-02 | 2018-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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 62+ | 79+ | 78+ | ❌ | 49+ | 46+ |
| 2017-10 | 2020-01 | 2020-06 | ➖ | 2017-11 | 2018-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)
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 64+ | 79+ | 78+ | 11.1+ | 51+ | 47+ |
| 2018-01 | 2020-01 | 2020-06 | 2018-04 | 2018-02 | 2018-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
| Chrome | Edge | Firefox | Safari | Opera | Opera Mobile |
| 66+ | 79+ | 54+ | ❌ | 53+ | 47+ |
| 2018-04 | 2020-01 | 2017-06 | ➖ | 2018-05 | 2018-07 |
新规范:要求Function.prototype.toString的返回值与声明的源代码完全现统,包括空格和注释;或者因某种原因,主机没有源代码,则要求返回一个原生函数字符串
