ES2020(即 ES11)上周(2020 年 6 月)已经正式发布,在此以前进入 Stage 4 的 10 项提案均已归入规范,成为 JavaScript 语言的新特性html
ES Module 迎来了一些加强:前端
正式支持了安全的链式操做:git
?.
可以在属性访问、方法调用前检查其是否存在??
提供了大数运算的原生支持:es6
一些基础 API 也有了新的变化:github
all
、race
同样具备短路特性index
、groups
等)this
的通用方法for-in
循环的某些行为咱们知道ES Module是一套静态的模块系统:正则表达式
The existing syntactic forms for importing modules are static declarations.
静态体如今:数组
They accept a string literal as the module specifier, and introduce bindings into the local scope via a pre-runtime "linking" process.
import/export
声明只能出如今顶层做用域,不支持按需加载、懒加载 例如:promise
if (Math.random()) { import 'foo'; // SyntaxError } // You can’t even nest `import` and `export` // inside a simple block: { import 'foo'; // SyntaxError }
这种严格的静态模块机制让基于源码的静态分析、编译优化有了更大的发挥空间:浏览器
This is a great design for the 90% case, and supports important use cases such as static analysis, bundling tools, and tree shaking.
但对另外一些场景很不友好,好比:安全
import
声明引用的全部模块(包括初始化暂时用不到的模块)都会在初始化阶段前置加载,影响首屏性能module-en
、module-zh
等)为了知足这些须要动态加载模块的场景,ES2020 推出了动态 import 特性(import()
):
import(specifier)
import()
“函数”输入模块标识specifier
(其解析规则与import
声明相同),输出Promise
,例如:
// 目标模块 ./lib/my-math.js function times(a, b) { return a * b; } export function square(x) { return times(x, x); } export const LIGHTSPEED = 299792458; // 当前模块 index.js const dir = './lib/'; const moduleSpecifier = dir + 'my-math.mjs'; async function loadConstant() { const myMath = await import(moduleSpecifier); const result = myMath.LIGHTSPEED; assert.equal(result, 299792458); return result; } // 或者不用 async & await function loadConstant() { return import(moduleSpecifier) .then(myMath => { const result = myMath.LIGHTSPEED; assert.equal(result, 299792458); return result; }); }
与import
声明相比,import()
特色以下:
module
,在普通的script
中也能使用注意,虽然长的像函数,但import()
其实是个操做符,由于操做符可以携带当前模块相关信息(用来解析模块表示),而函数不能:
Even though it works much like a function,
import()
is an operator: in order to resolve module specifiers relatively to the current module, it needs to know from which module it is invoked. A normal function cannot receive this information as implicitly as an operator can. It would need, for example, a parameter.
另外一个 ES Module 新特性是import.meta
,用来透出模块特定的元信息:
import.meta, a host-populated object available in Modules that may contain contextual information about the Module.
好比:
__dirname
、__filename
script
标签:例如浏览器支持的document.currentScript
process.mainModule
诸如此类的元信息均可以挂到import.meta
属性上,例如:
// 模块的 URL(浏览器环境) import.meta.url // 当前模块所处的 script 标签 import.meta.scriptElement
但须要注意的是,规范并无明肯定义具体的属性名和含义,都由具体实现来定,因此特性提案里的但愿浏览器支持的这两个属性未来可能支持也可能不支持
P.S.import.meta
自己是个对象,原型为null
第三个 ES Module 相关的新特性是另外一种模块导出语法:
export * as ns from "mod";
同属于export ... from ...
形式的聚合导出,做用上相似于:
import * as ns from "mod"; export {ns};
但不会在当前模块做用域引入目标模块的各个 API 变量
P.S.对照import * as ns from "mod";
语法,看起来像是 ES6 模块设计中排列组合的一个疏漏;)
至关实用的一个特性,用来替代诸如此类冗长的安全链式操做:
const street = user && user.address && user.address.street;
可换用新特性(?.
):
const street = user?.address?.street;
语法格式以下:
obj?.prop // 访问可选的静态属性 // 等价于 (obj !== undefined && obj !== null) ? obj.prop : undefined obj?.[«expr»] // 访问可选的动态属性 // 等价于 (obj !== undefined && obj !== null) ? obj[«expr»] : undefined func?.(«arg0», «arg1») // 调用可选的函数或方法 // 等价于 (func !== undefined && func !== null) ? func(arg0, arg1) : undefined
P.S.注意操做符是?.
而不是单?
,在函数调用中有些奇怪alert?.()
,这是为了与三目运算符中的?
区分开
机制很是简单,若是出如今问号前的值不是undefined
或null
,才执行问号后的操做,不然返回undefined
一样具备短路特性:
// 在 .b?.m 时短路返回了 undefined,而不会 alert 'here' ({a: 1})?.a?.b?.m?.(alert('here'))
与&&
相比,新的?.
操做符更适合安全进行链式操做的场景,由于:
?.
遇到属性/方法不存在就返回undefined
,而不像&&
同样返回左侧的值(几乎没什么用)?.
只针对null
和undefined
,而&&
遇到任意假值都会返回,有时没法知足须要例如经常使用的正则提取目标串,语法描述至关简洁:
'string'.match(/(sing)/)?.[1] // undefined // 以前须要这样作 ('string'.match(/(sing)/) || [])[1] // undefined
还能够配合 Nullish coalescing Operator 特性填充默认值:
'string'.match(/(sing)/)?.[1] ?? '' // '' // 以前须要这样作 ('string'.match(/(sing)/) || [])[1] || '' // '' // 或者 ('string'.match(/(sing)/) || [, ''])[1] // ''
一样引入了一种新的语法结构(??
):
actualValue ?? defaultValue // 等价于 actualValue !== undefined && actualValue !== null ? actualValue : defaultValue
用来提供默认值,当左侧的actualValue
为undefined
或null
时,返回右侧的defaultValue
,不然返回左侧actualValue
相似于||
,主要区别在于??
只针对null
和undefined
,而||
遇到任一假值都会返回右侧的默认值
新增了一种基础类型,叫BigInt
,提供大整数运算支持:
BigInt is a new primitive that provides a way to represent whole numbers larger than 2^53, which is the largest number Javascript can reliably represent with the Number primitive.
JavaScript 中Number
类型所能准确表示的最大整数是2^53
,不支持对更大的数进行运算:
const x = Number.MAX_SAFE_INTEGER; // 9007199254740991 即 2^53 - 1 const y = x + 1; // 9007199254740992 正确 const z = x + 2 // 9007199254740992 错了,没变
P.S.至于为何是 2 的 53 次方,是由于 JS 中数值都以 64 位浮点数形式存放,刨去 1 个符号位,11 个指数位(科学计数法中的指数),剩余的 52 位用来存放数值,2 的 53 次方对应的这 52 位所有为 0,能表示的下一个数是2^53 + 2
,中间的2^53 + 1
没法表示:
[caption id="attachment_2213" align="alignnone" width="625"]<img src="http://www.ayqy.net/cms/wordpress/wp-content/uploads/2020/06/JavaScript-Max-Safe-Integer-1024x518.jpg" alt="JavaScript Max Safe Integer" width="625" height="316" class="size-large wp-image-2213" /> JavaScript Max Safe Integer[/caption]
具体解释见BigInts in JavaScript: A case study in TC39
BigInt
类型的出现正是为了解决此类问题:
9007199254740991n + 2n // 9007199254740993n 正确
引入的新东西包括:
n
表示大整数,例如9007199254740993n
、0xFFn
(二进制、八进制、十进制、十六进制字面量统统能够后缀个n
变成BigInt
)bigint
基础类型:typeof 1n === 'bigint'
BigInt
例如:
// 建立一个 BigInt 9007199254740993n // 或者 BigInt(9007199254740993) // 乘法运算 9007199254740993n * 2n // 幂运算 9007199254740993n ** 2n // 比较运算 0n === 0 // false 0n === 0n // true // toString 123n.toString() === '123'
P.S.关于 BigInt API 细节的更多信息,见ECMAScript feature: BigInt – arbitrary precision integers
须要注意的是BigInt
不能与Number
混用进行运算:
9007199254740993n * 2 // 报错 Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions
而且BigInt
只能表示整数,因此除法直接取整(至关于Math.trunc()
):
3n / 2n === 1n
基础 API 也有一些新的变化,包括 Promise、字符串正则匹配、for-in
循环等
继Promise.all、Promise.race以后,Promise
新增了一个静态方法叫allSettled
:
// 传入的全部 promise 都有结果(从 pending 状态变成 fulfilled 或 rejected)以后,触发 onFulfilled Promise.allSettled([promise1, promise2]).then(onFulfilled);
P.S.另外,any
也在路上了,目前(2020/6/21)处于 Stage 3
相似于all
,但不会由于某些项rejected
而短路,也就是说,allSettled
会等到全部项都有结果(不管成功失败)后才进入Promise
链的下一环(因此它必定会变成 Fulfilled 状态):
A common use case for this combinator is wanting to take an action after multiple requests have completed, regardless of their success or failure.
例如:
Promise.allSettled([Promise.reject('No way'), Promise.resolve('Here')]) .then(results => { console.log(results); // [ // {status: "rejected", reason: "No way"}, // {status: "fulfilled", value: "Here"} // ] }, error => { // No error can get here! })
字符串处理的一个常见场景是想要匹配出字符串中的全部目标子串,例如:
const str = 'es2015/es6 es2016/es7 es2020/es11'; str.match(/(es\d+)\/es(\d+)/g) // 顺利获得 ["es2015/es6", "es2016/es7", "es2020/es11"]
match()
方法中,正则表达式所匹配到的多个结果会被打包成数组返回,但没法得知每一个匹配除结果以外的相关信息,好比捕获到的子串,匹配到的index
位置等:
This is a bit of a messy way to obtain the desired information on all matches.
此时只能求助于最强大的exec
:
const str = 'es2015/es6 es2016/es7 es2020/es11'; const reg = /(es\d+)\/es(\d+)/g; let matched; let formatted = []; while (matched = reg.exec(str)) { formatted.push(`${matched[1]} alias v${matched[2]}`); } console.log(formatted); // 获得 ["es2015 alias v6", "es2016 alias v7", "es2020 alias v11"]
而 ES2020 新增的matchAll()
方法就是针对此类种场景的补充:
const results = 'es2015/es6 es2016/es7 es2020/es11'.matchAll(/(es\d+)\/es(\d+)/g); // 转数组处理 Array.from(results).map(r => `${r[1]} alias v${r[2]}`); // 或者从迭代器中取出直接处理 // for (const matched of results) {} // 获得结果同上
注意,matchAll()
不像match()
同样返回数组,而是返回一个迭代器,对大数据量的场景更友好
JavaScript 中经过for-in
遍历对象时 key 的顺序是不肯定的,由于规范没有明肯定义,而且可以遍历原型属性让for-in
的实现机制变得至关复杂,不一样 JavaScript 引擎有各自根深蒂固的不一样实现,很难统一
因此 ES2020 不要求统一属性遍历顺序,而是对遍历过程当中的一些特殊 Case 明肯定义了一些规则:
具体见13.7.5.15 EnumerateObjectProperties
最后一个新特性是globalThis
,用来解决浏览器,Node.js 等不一样环境下,全局对象名称不统一,获取全局对象比较麻烦的问题:
var getGlobal = function () { // the only reliable means to get the global object is // `Function('return this')()` // However, this causes CSP violations in Chrome apps. if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); };
globalThis
做为统一的全局对象访问方式,老是指向全局做用域中的this
值:
The global variable globalThis is the new standard way of accessing the global object. It got its name from the fact that it has the same value as this in global scope.
P.S.为何不叫global
?是由于global
可能会影响现有的一些代码,因此另起一个globalThis
避免冲突
至此,ES2020 的全部新特性都清楚了
比起ES2019,ES2020 算是一波大更新了,动态 import、安全的链式操做、大整数支持……全都加入了豪华午饭
关注「前端向后」微信公众号,你将收获一系列「用心原创」的高质量技术文章,主题包括但不限于前端、Node.js以及服务端技术
本文首发于 ayqy.net ,原文连接:http://www.ayqy.net/blog/es2020/