2020年来了,ES2020也来了,新增的几个特性到底香不香,来一起细细品一下。
globalThis
我们在书写js的时候,全局this
会因为运行环境不同而不同。如果运行环境是浏览器,关键字为window
,如果是node
环境,则全局this
关键字为global
;在方法内部可以用this
本身指代this
。如果希望代码在上述任意环境中都能够运行,就要我们去手动判断环境类型然后决定使用哪个关键字,像下面这样:
//
const getGlobalThis = () => {
if (typeof globalThis !== 'undefined') return globalThis;
if (typeof self !== 'undefined') return self;
if (typeof window !== 'undefined') return window;
if (typeof global !== 'undefined') return global;
if (typeof this !== 'undefined') return this;
// 注意:这样判断仍有可能返回错误结果!
throw new Error('Unable to locate global `this`');
};
const theGlobalThis = getGlobalThis();
是不是很麻烦,但是!聪明绝顶的程序员们是不允许这么麻烦而又不靠谱的事情存在的,所以globalThis
应运而生。无论是浏览器,node或者其他什么环境,无论是class还是模块都可以用globalThis
指定当前环境的全局this
,是不是很惊喜?
应用了新特性的代码也许不需要使用全局this
。Js模块中可以使用import
和export
功能替代繁琐的全局状态。但是globalThis
对于一些polyfills 等还是很有用的。
支持状况:
- Chrome 71+
- Firefox65+
- Safari 12.1+
- Node 12+
- Babel
Promise 组合方法
Es6中已经支持了promise 组合的两个静态方法Promise.all
和Promise.race
;
现在有了两个新的提案:Promise.allSettled
和Promise.any
。这样js就有了四种不同功能的promise组合方法。
下面一一介绍一下这四种方法
Promise.all
Promise .all
用来提示是不是数组中所有的promises都已经fullfilled或者有任何一个rejects。
举个栗子:假如用户点击按钮之后,你需要下载一些样式。这个过程中所有的样式文件HTTP请求需要同时启动:
const promises = [
fetch('/component-a.css'),
fetch('/component-b.css'),
fetch('/component-c.css'),
];
try {
const styleResponses = await Promise.all(promises);
enableStyles(styleResponses);
renderNewUi();
} catch (reason) {
displayError(reason);
}
仅仅当所有的样式文件都请求成功了才会刷新UI,任何文件请求失败都会展示错误信息,不用再等待其他请求结果。
支持状况:
- Chrome 32+
- Firefox29+
- Safari 8+
- Node 0.12+
- Babel
Promise.race
Promise.race用来提示promises组合中任何一个返回成功或者失败的情况都会返回对应结果。
let performHeavyComputation=()=>new Promise((res,rej)=>{
// 假装大量计算耗费3秒
setTimeout(()=>res('success'),3000)
})
let rejectAfterTimeout=(time)=>new Promise((res,rej)=>{
setTimeout(()=>rej('Computation error'),time)
})
try {
const result = await Promise.race([
performHeavyComputation(),
rejectAfterTimeout(2000),
]);
console.log('result');
} catch (error) {
console.log(error);
}
执行大量计算任务时可能会耗费很长时间,我们同时向数组中传入一个两秒后reject的promise。当计算任务成功后,会向下执行;如果两秒后还没有任何返回值,就会返回第二个promise的reject结果,展示error信息。
支持状况:
- Chrome 32+
- Firefox29+
- Safari 8+
- Node 0.12+
- Babel
Promise.allSettled
Promise.allSettled用来提示promises组合中所有的返回状态,无论是fulfilled还是rejected。当你不关心promise的返回状态,而是关心整体的请求是不是执行完的时候,它很有用。
举个栗子,当进行一系列独立的API请求时,可以使用这个方法来确认是不是所有请求都已经执行完了,这样就可以执行后续取消加载状态操作。
const promises = [
new Promise((res)=>{setTimeout(()=>res('done1')),1000}),
new Promise((res,rej)=>{setTimeout(()=>rej('error2')),1500}),
new Promise((res)=>{setTimeout(()=>res('done1')),2000}),
];
const start = new Date()
//假设一些请求成功了,一些失败了。
const result=await Promise.allSettled(promises);
// 根据最终返回状态执行取消加载状态操作。
const useTime = new Date() - start
console.log(result, `${end}秒`);
// ->(3) [{…}, {…}, {…}]
// 0: {status: "fulfilled", value: "done1"}
// 1: {status: "rejected", reason: "error2"}
// 2: {status: "fulfilled", value: "done1"}
// length: 3
// __proto__: Array(0)
// 2秒
上述代码在没有考虑是否返回成功或者失败的情况下,最长执行时间为2秒,最终我们打印结果也为2秒。
支持状况:
- Chrome 76+
- Firefox71+
- Safari 13+
- Node 12.9.0+
- Babel
Promise.any(提案)
Promise.any
用来提示任何一个promise fulfilled,这和promise.race有点相似,但是它并不关心是不是有promise rejected了,但是目前仅仅在提案阶段,并没有浏览器支持。
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
// 任意promise fulfilled.
console.log(first);
// → e.g. 'b'
} catch (error) {
// 所有的promise都rejected.
console.log(error);
}
上述代码检测并打印最早执行成功的promise的返回结果。当所有请求都失败才会执行catch回调。
BigInt: 任意精度整数类型
BigInt是js中新的数字类型-任意精度整数,应用BigInt类型,可以安全的存储和操作大于最大安全数值的数字(Number.MAX_VALUE
)。
有时候我们在操作一些很大的数值的时候,例如较大的IDs和高精度时间戳,由于js的数字大小有限制,导致最终的计算结果出现偏差甚至bug。面对这些情况,以前的做法一般是将其转化为字符串,但是应用了BigInt之后,这些数据可以用数字方法正确呈现。
Js中数字存在一个最大安全整数
Number.MAX_SAFE_INTEGER — 2**53-1
,一旦大于这个数字,计算结果就失真了:
const max = Number.MAX_SAFE_INTEGER;
// → 9_007_199_254_740_991
首先在这个基础上+1
max + 1;
// → 9_007_199_254_740_992 ✅
可以看到返回结果是正确的,但是如果+2呢
max + 2;
// → 9_007_199_254_740_992 ❌
显然是错误的,任何超过安全数值的计算都会失败。所以这时候就该BigInt上场了。
在整数类型中添加后缀'n'就会得到BigInt类型。也可以使用BigInt(number)方法将Number转换为BigInt。BigInt(123) === 123n
。上述问题我们运用BigInt解决的话:
BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// → 9_007_199_254_740_993n ✅
当两个数相乘的时候:
1234567890123456789 * 123;
// → 151851850485185200000 ❌
显然结果是错误的,但是运用BigInt:
1234567890123456789n * 123n;
// → 151851850485185185047n ✅
这次的结果就是正确的。
安全数字对于number类型的封印在BigInt身上解除了。 因为Bigint是js的一种新的类型,所以可以用typeof来获取它的数据类型:
typeof 123;
// → 'number'
typeof 123n;
// → 'bigint'
也正是因为它是一种新的数据类型,所以BigInt并不严格等于Number。 在计算时尽量避免BigInt和Number类型的混用。
42n === BigInt(42);
// → true
42n == 42;
// → true
42n === 42;
// → false
但是这并不影响它转化为布尔值进行条件判断的功能:
if (0n) {
console.log('if');
} else {
console.log('else');
}
// →0n判断为假,所以打印'else'.
Dynamic import()
动态引用方法。来对比一下静态引用:
// 默认导出
export default () => {
console.log('Hi from the default export!');
};
// 导出 `doStuff`
export const doStuff = () => {
console.log('Doing stuff…');
};
Static import用法:
<script type="module">
import * as module from './utils';
module.default();
// → 打印 'Hi from the default export!'
module.doStuff();
// → 打印 'Doing stuff…'
</script>
Dynamic import()引入了新的依托于函数的import方式。import(moduleSpecifier)在获取、实例化并解析所有的模块依赖之后,创建并返回一个promise,fulfilled状态下成功函数参数为当前引用模块命名空间对象,这和被引用模块本身是一致的。下面是用法:
<script type="module">
const moduleSpecifier = './utils.mjs';
import(moduleSpecifier)
.then((module) => {
module.default();
// 打印 'Hi from the default export!'
module.doStuff();
// 打印 'Doing stuff…'
});
</script>
因为import()返回的是promise,所以他也可以用async/await来代替then这种回调函数的形式:
<script type="module">
(async () => {
const moduleSpecifier = './utils.mjs';
const module = await import(moduleSpecifier)
module.default();
// → logs 'Hi from the default export!'
module.doStuff();
// → logs 'Doing stuff…'
})();
</script>
下面是一个Dynamic import()根据导航实现懒加载的例子:
<!DOCTYPE html>
<meta charset="utf-8">
<title>My library</title>
<nav>
<a href="books.html" data-entry-module="books">Books</a>
<a href="movies.html" data-entry-module="movies">Movies</a>
<a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>
<main>按需加载</main>
<script>
const main = document.querySelector('main');
const links = document.querySelectorAll('nav > a');
for (const link of links) {
link.addEventListener('click', async (event) => {
event.preventDefault();
try {
const module = await import(`/${link.dataset.entryModule}.mjs`);
// 模块导出 `loadPageInto`方法.
module.loadPageInto(main);
} catch (error) {
main.textContent = error.message;
}
});
}
</script>
动态引入的懒加载是非常有用的。相比于静态加载,它可以防止加载很多不必要的资源。
上述就是ES2020新增的几个特性,不管你觉得香不香,反正我觉得挺香的。如果有不对之处,欢迎指正,如果觉得有用,就给我个大拇指吧o( ̄▽ ̄)o
生活的一部分是工作,工作的一部分是解决问题取悦生活,所以好好生活,好好工作,好好热爱(●ˇ∀ˇ●)