文章来源:方凳雅集
前言
自从ES6发布之后,TC39这个负责开发ECMAScript标准的机构每年都会发布新特性。
新功能概览
Optional Chaining
Nullish coalescing Operator
globalThis
Promise.allSettled
String.prototype.matchAll
BigInt
for-in mechanics
import()
详细介绍
可选链运算符 - Optional Chaining
有时候为了访问深层嵌套的属性,我们需要写一个很长的&&链去检查每个属性是否存在,代码如下:
var price = result && result.body && result.body.data && result.body.data.price;
事实上在日常开发中我们经常要这么做,而且如果我们不这么做,那么很可能导致程序异常。为了简化上述的代码,于是乎新增了可选链运算符,代码如下:
var price = result?.body?.data?.price;
可选运算符不仅能作用到属性上,也能作用到方法。
const result = {};
const price = result?.body?.data?.price;
const user = {};
const name = user?.getInfo?.().getName?.();
const respone = {};
const firstProduct = respone?.body?.data?.products?.[0];
空值合并运算符 - Nullish coalescing Operator
获取对象的属性的时候,我们经常需要为 null/undefined 的属性设置默认值。目前正常的情况下我们可以使用 || 运算符,例如:
var user = {};
var name = user.name || 'p9';
但是,这么做会导致false,0,空字符串等属性会被覆盖掉,这是我们不愿意看到的,这时候就可以使用空值合并运算符来避免。例如:
const response = {
settings: {
nullValue: null,
height: 400,
animationDuration: 0,
headerText: '',
showSplashScreen: false
}
};
const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // result: 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; // result: 'some other default'
const headerText = response.settings.headerText ?? 'Hello, world!'; // result: ''
const animationDuration = response.settings.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // result: false
标准化的全局对象 - globalThis
在不同环境下是很难轻便的获取全局对象的。因为,在浏览器侧,全局对象可能是 window 或者 self 或者 this 或者 frames;在 node,全局对象可以从 global 或者 this 获取;在非严格模式下,方法里面的 this 指向全局对象,但是在严格模式下又是 undefined ;当然,我们也可以通过 Function('return this')() 获取全局对象,但是在某些CSP设置下的浏览器中又不能访问,例如Chrome Apps。这些问题就导致我们可以通过如下代码:
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。例如:
function canMakeHTTPRequest() {
return typeof globalThis.XMLHttpRequest === 'function';
}
console.log(canMakeHTTPRequest());
// expected output (in a browser): true
Promise.allSettled
相信大家都知道,Promise.all 是可以并发执行多个任务的,但是 Promise.all 有个缺陷,那就是只要其中有一个任务失败了,就会直接进入 catch 的流程,想象一下,我们有两个请求,一个失败了,结果都挂了,这不是我们想要的。
const promise1 = new Promise((resolve) => setTimeout(resolve, 200, 'good'));
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'reject'));
const promises = [promise1, promise2];
Promise.all(promises).
then((ret)=>{
// 不会触发
debugger
})
.catch((ret)=>{
console.log(ret); // reject
})
.finally(()=>{
// 会触发,获取不到我们想要的内容
});
我们需要一个方法来保证如果一个任务失败了,其它任务还会继续执行,这样才能最大限度的保障业务的可用性
。这时候Promise.allSettled 出现了,Promise.allSettled 会返回一个 promise,这个返回的 promise 会在所有的输入的 promise resolve 或者 reject 之后触发。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result)));
// expected output:
// {status: "fulfilled", value: 3}
// {status: "rejected", reason: "foo"}
String.prototype.matchAll
matchAll 的行为跟 match 有点像,但 matchAll 返回的是一个迭代器,通过这个迭代器我们可以快速的获取匹配的所有内容。
在了解matchAll之前先来看看 match 有什么问题。首先我们先来看一下代码:
const regex = /t(e)(st(\d?))/g;
const string = 'test1test2';
const results = string.match(regex);
console.log(results);
// → ['test1', 'test2']
执行完之后可以发现,匹配到的内容是 test1,test2。而把正则的全局匹配选项去掉之后,得到的内容却是:
['test1', 'e', 'st1', '2', index: 0, input: 'test1test2', groups: undefined]
如果需要 test2 相关的匹配,这时候就没有了,那么怎么获取到 test2 相关的匹配?在此之前,我们可以使用 replace ,当然,代码长了点:
var regex = /t(e)(st(\d?))/g;
var string = 'test1test2';
var matches = [];
string.replace(regex, function () {
var match = Array.prototype.slice.call(arguments, 0, -2);
match.input = arguments[arguments.length - 1];
match.index = arguments[arguments.length - 2];
matches.push(match);
// example: ['test1', 'e', 'st1', '1'] with properties `index` and `input`
});
matches;
// ["test1", "e", "st1", "1", input: "test1test2", index: 0]
// ["test2", "e", "st2", "2", input: "test1test2", index: 5]
为了简化上面又臭又长的代码,于是乎就有了 matchAll
let regexp = /t(e)(st(\d?))/g;
let str = 'test1test2';
let array = [...str.matchAll(regexp)];
console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]
console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]
BigInt
BigInt 是一种内置对象,它提供了一种方法来表示大于 253 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。
相关语法如下,我们可以通过在数字后面添加n或者通过 BigInt() 来创建 BigInt 变量。
const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);
// 9007199254740991n
typeof alsoHuge === 'bigint' // true
bigint 跟 number 一样,也支持+,*,-,**和%等运算符:
const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// ↪ 9007199254740991
const maxPlusOne = previousMaxSafe + 1n;
// ↪ 9007199254740992n
const theFuture = previousMaxSafe + 2n;
// ↪ 9007199254740993n, this works now!
const multi = previousMaxSafe * 2n;
// ↪ 18014398509481982n
const subtr = multi – 10n;
// ↪ 18014398509481972n
const mod = multi % 10n;
// ↪ 2n
const bigN = 2n ** 54n;
// ↪ 18014398509481984n
bigN * -1n
// ↪ –18014398509481984n
bigint 可以像平时一样跟 number 比较大小:
1n < 2
// ↪ true
2n > 1
// ↪ true
2 > 2
// ↪ false
2n > 2
// ↪ false
2n >= 2
// ↪ true
转换成布尔类型的时候,bigint 的行为跟 number 是一样的:
if (0n) {
console.log('Hello from the if!');
} else {
console.log('Hello from the else!');
}
// ↪ "Hello from the else!"
0n || 12n
// ↪ 12n
0n && 12n
// ↪ 0n
Boolean(0n)
// ↪ false
Boolean(12n)
// ↪ true
!12n
// ↪ false
!0n
bigint 可以像 number 那样转换成字符串的
1n + '2'
// ↪ "12"
'2' + 1n
// ↪ "21"
需要注意的点
bigint 跟 number不严格相等
0n === 0
// ↪ false
0n == 0
// ↪ true
bigint 不能跟 number一起运算
1n + 2
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
1n * 2
// ↪ TypeError: Cannot mix BigInt and other types, use explicit conversions
不能使用+把bigint转换成number
+1n
// ↪ TypeError: Cannot convert a BigInt value to a number
Number(1n)
// ↪ 1
for-in mechanics
以前在不同的引擎下for in循环出来的内容顺序是可能不一样的,现在标准化了。
异步加载模块 - import()
有时候我们需要动态加载一些模块,这时候就可以使用 import() 了
// logger.js
export default {
log: function() {
console.log(...arguments);
}
};
import('./logger.js')
.then((module)=>{
module.default.log('p9');
})
当然,也可以使用await进行等待
let module = await import('/modules/my-module.js');
后记
授人以鱼不如授人以渔,以后要看ES的新特性,可以查看GitHub上的文档。
参考资料
MDN
https://github.com/tc39/proposals/blob/master/finished-proposals.md
https://developers.google.com/web/updates/2019/02/string-matchall
https://blog.logrocket.com/5-es2019-features-you-can-use-today/