ES11来了,还学得动吗?
写在前面
ES2020(即 ES11)上周(2020 年 6 月)已经正式发布,在此之前进入 Stage 4 的 10 项提案均已纳入规范,成为 JavaScript 语言的新特性
一.特性一览
ES Module 迎来了一些增强:
import():一种可以用动态模块标识异步引入模块的的语法
import.meta:一个对象,用来携带模块相关的元信息
export * as ns from "mod";:一种新的聚合导出语法
正式支持了安全的链式操作:
Optional chaining:新运算符
?.
能够在属性访问、方法调用前检查其是否存在Nullish coalescing Operator:用来提供默认值的新运算符
??
提供了大数运算的原生支持:
- BigInt – arbitrary precision integers:一种新的基础数值类型,支持任意精度的整数运算
一些基础 API 也有了新的变化:
Promise.allSettled:一个新的 Promise 组合器,不像
all
、race
一样具有短路特性String.prototype.matchAll:以迭代器的形式返回全局匹配模式下的正则表达式匹配到的所有结果(
index
、groups
等)globalThis:访问全局作用域
this
的通用方法for-in mechanics:规范
for-in
循环的某些行为
二.ES Module 增强
动态 import
我们知道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
声明只能出现在顶层作用域,不支持按需加载、懒加载静态标识:模块标识只能是字符串字面量,不支持运行时动态计算而来的模块名
例如:
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.
import.meta
另一个 ES Module 新特性是import.meta
,用来透出模块特定的元信息:
import.meta, a host-populated object available in Modules that may contain contextual information about the Module.
比如:
模块的 URL 或文件名:例如 Node.js 里的
__dirname
、__filename
所处的
script
标签:例如浏览器支持的document.currentScript
入口模块:例如 Node.js 里的
process.mainModule
诸如此类的元信息都可以挂到import.meta
属性上,例如:
// 模块的 URL(浏览器环境)
import.meta.url
// 当前模块所处的 script 标签
import.meta.scriptElement
但需要注意的是,规范并没有明确定义具体的属性名和含义,都由具体实现来定,所以特性提案里的希望浏览器支持的这两个属性将来可能支持也可能不支持
P.S.import.meta
本身是个对象,原型为null
export-ns-from
第三个 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 模块设计中排列组合的一个疏漏;)
三.链式操作支持
Optional Chaining
相当实用的一个特性,用来替代诸如此类冗长的安全链式操作:
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] // ''
Nullish coalescing Operator
同样引入了一种新的语法结构(??
):
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.
BigInt
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"] 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
基础 API 也有一些新的变化,包括 Promise、字符串正则匹配、for-in
循环等
Promise.allSettled
继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!
})
String.prototype.matchAll
字符串处理的一个常见场景是想要匹配出字符串中的所有目标子串,例如:
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()
一样返回数组,而是返回一个迭代器,对大数据量的场景更友好
for-in 遍历机制
JavaScript 中通过for-in
遍历对象时 key 的顺序是不确定的,因为规范没有明确定义,并且能够遍历原型属性让for-in
的实现机制变得相当复杂,不同 JavaScript 引擎有各自根深蒂固的不同实现,很难统一
所以 ES2020 不要求统一属性遍历顺序,而是对遍历过程中的一些特殊 Case 明确定义了一些规则:
遍历不到 Symbol 类型的属性
遍历过程中,目标对象的属性能被删除,忽略掉尚未遍历到却已经被删掉的属性
遍历过程中,如果有新增属性,不保证新的属性能被当次遍历处理到
属性名不会重复出现(一个属性名最多出现一次)
目标对象整条原型链上的属性都能遍历到
具体见13.7.5.15 EnumerateObjectProperties
globalThis
最后一个新特性是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、安全的链式操作、大整数支持……全都加入了豪华午餐
参考资料
ES11来了,还学得动吗?的更多相关文章
- C#9.0 终于来了,您还学的动吗? 带上VS一起解读吧!(应该是全网第一篇)
一:背景 1. 讲故事 好消息,.NET 5.0 终于在2020年6月10日发布了第五个预览版,眼尖的同学一定看到了在这个版本中终于支持了 C# 9.0,此处有掌声,太好了!!! .Net5官方链接 ...
- 还学的动吗? 盘点下Vue.js 3.0.0 那些让人激动的功能
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文出处:https://blog.bitsrc.io/vuejs-3-0-0-beta-features- ...
- [干货] 有了微信小程序,谁还学ReactNative?
版权声明:本文由贺嘉原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/145 来源:腾云阁 https://www.qclou ...
- 为什么有mac地址还学要有IP地址??
历史原因:早期的以太网只有集线器 ,没有交换机,所以发出去的包能被以太网内的所有机器监听到,因此要附带上MAC地址,每个机器只需要接受与自己MAC地址相匹配的包. 个人感觉上面的说法并不是太准确.找明 ...
- C++入门(2):为何还学C++?
本文首发 | 公众号:lunvey 提及编程语言,最近很火的当属Python和Java,似乎C++没落了,真的是这样吗? 转行做程序员,掌握一门编程语言,也就是职业技能,我相信更多的是在乎未来发展而不 ...
- Phaser都不懂,还学什么多线程
前面的文章中我们讲到了CyclicBarrier.CountDownLatch的使用,这里再回顾一下CountDownLatch主要用在一个线程等待多个线程执行完毕的情况,而CyclicBarrier ...
- 萌新看过来,你还学不懂VScode插件吗?
一.前言 VSCode是微软家一个非常轻量化的编辑器,体量虽轻,但是却有异常强大的功能.原因在于VSCode许多强大功能都是基于插件实现的,IDE只提供一个最基本的框架和基本功能,我们需要使用插件来丰 ...
- Vue 3.0 升级指南
本文由葡萄城技术团队原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 2020年9月18日Vue 3.0正式发布,距离2016年10月1日Vue ...
- 像Swing这种已经不太用的技术,大学还在教,到底要不要学?
一直以来,写日常问题.前沿技术和架构思考类的文章比较多,今天为什么突然来说说Swing这个陈年老技术呢? 因为在CSDN上看到了这样的一篇文章: 可以看到作者对于学Swing还是挺愤怒的,不过确实Sw ...
随机推荐
- Java实现 蓝桥杯VIP 算法训练 麦森数
算法训练 麦森数 时间限制:1.0s 内存限制:256.0MB 问题描述 形如2P-1的素数称为麦森数,这时P一定也是个素数.但反过来不一定,即如果P是个素数,2P-1不一定也是素数.到1998年底, ...
- Java实现 LeetCode 55 跳跃游戏
55. 跳跃游戏 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 判断你是否能够到达最后一个位置. 示例 1: 输入: [2,3,1,1,4] ...
- 什么是 JVM ?
什么是 JVM ? 解析:不仅仅是基本概念,还有 JVM 的作用. 答:JVM,即 Java Virtual Machine,Java 虚拟机.它通过模拟一个计算机来达到一个计算机所具有的的计算功能. ...
- Python基础语法之“print()”函数
print()函数是Python入门的第一个必学知识点,它经常被用来调试已写的代码,检验效果,今天小老鼠就带你盘点一下print()函数在Python中如何使用. print()函数的工作流程是这样的 ...
- Spring源码之自动装配
我们使用Spring开发过程中经常会用到Autowired注解注入依赖的bean,这部分也是面试的热点问题之一.今天咱们一起来深入研究下自动注入的背后实现原理.首先上一个例子,如下所示: @RestC ...
- @gym - 100958J@ Hyperrectangle
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个大小为 \(l_1\times l_2 \dots l_ ...
- CentOS7.5搭建spark2.3.1集群
一 下载安装包 1 官方下载 官方下载地址:http://spark.apache.org/downloads.html 2 安装前提 Java8 安装成功 zookeeper 安 ...
- (九)maven-compiler-plugin 插件详解
<plugin> <!-- 指定maven编译的jdk版本,如果不指定,maven3默认用jdk 1.5 maven2默认用jdk1.3 --> <groupId> ...
- MySQL LIMIT:限制查询结果的记录条数
基本的语法格式如下: <LIMIT> [<位置偏移量>,] <行数> LIMIT 接受一个或两个数字参数.参数必须是一个整数常量.如果给定两个参数,第一个参数指定第 ...
- post请求头的常见类型
1.application/json(JSON数据格式) xhr.setRequestHeader("Content-type","application/json; c ...