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
:返回false
fromIndex < 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.
对象自身的所有可枚举属性值的数组;顺序与使用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)
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
的返回值与声明的源代码完全现统,包括空格和注释;或者因某种原因,主机没有源代码,则要求返回一个原生函数字符串