什么是 TC39?
TC39 是技术委员会第 39 号,是 ECMAScript 规范下的 JavaScript 语言标准化的机构,负责开发 JavaScript 的委员会。
TC39 提案过程
每个 ECMAScript 的提案都将经过以下阶段,从 Stage 0 开始,从一个阶段到下一个阶段的进展必须得到 TC39 的批准
stage-0:还是一个设想,只能由TC39成员或TC39贡献者提出
stage-1::提案阶段,比较正式的提议,只能由TC39成员发起,这个提案要解决的问题必须有正式的书面描述。
stage-2:草案,有了初始规范,必须对功能语法和语义进行正式描述,包括一些实验性的实现。
stage-3:候选,该提议基本已经实现,需要等待实验验证,用户反馈及验收测试通过。
stage-4:已完成,必须通过 Test262 验收测试,下一步就纳入ECMA标准。
学不动也要学的新特性
ES2020(即 ES11)(2020 年 6 月)已经正式发布,在此之前进入 Stage 4 的 10 项提案均已纳入规范,成为 JavaScript 语言的新特性
globalThis :通用顶层对象
● globalThis:他的作用作为顶层对象,指向全局环境下的thisBrowser:顶层对象是window
● Node:顶层对象是global
● WebWorker:顶层对象是self
● 以上三者:通用顶层对象是globalThis
● 而 globalThis 目的就是提供一种标准化方式访问全局对象,有了 globalThis 后,你可以在任意上下文,任意时刻都能获取到全局对象。
● 如果您在浏览器上,globalThis将为window,如果您在Node上,globalThis则将为global。因此,不再需要考虑不同的环境问题。
● globalThis 是一个全新的标准方法用来获取全局 this 。之前开发者会通过如下的一些方法获取:
// 之前的解决方案
const getGlobal = function(){
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')
}
// 定义一个全局对象v = { value:true } ,ES10用如下方式定义
globalThis.v = { value:true }
// worker.js
globalThis === self
// node.js
globalThis === global
// browser.js
globalThis === window
BigInt :任何位数的整数(新增的数据类型,使用n结尾)
javascript 在计算时只能计算Number.MIN_SAFE_INTEGER 至Number.MAX_SAFE_INTEGER中的值,超出这个范围的整数计算或者表示会丢失精度。
所以es11加入了BigInt, 是第七个原始数据类型,用于精确计算大数整形计算,不会丢失精度
一些BigInt方法
● BigInt():转换普通数值为BigInt类型
● BigInt.parseInt():近似于Number.parseInt(),将一个字符串转换成指定进制的BigInt类型
注意点
● BigInt同样可使用各种进制表示,都要加上后缀
● BigInt与Number是两种值,它们之间并不是全等的,可以进行比较,但不能进行计算
● typeof运算符对于BigInt类型的数据返回bigint
链判断操作符(?.)
当我们想要查找多层级的对象时,需要进行冗余的前置校验,否则容易报错导致程序停止运行
而使用es11中的链判断操作符可以大大简化这种前置校验
// 在es11之前的写法
let name = user && user.info && user.info.name;
//es11之后的写法
let name = name = user?.info?.name;
可选链中的 ? 表示如果问号左边表达式有值, 就会继续查询问号后面的字段。根据上面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全.
如果user或user.info是null/undefined,表达式将会短路计算直接返回undefined
空位合并操作符
当我们查询某个属性时,经常会给没有该属性就设置一个默认的值,比如下面两种方式
let c = a ? a : b // 方式1
let c = a || b // 方式2
但是上面两种方法有一个明显的弊端,它会覆盖所有的假值( 0 , ' ' , false) ,这些值在某些情况下可能是有效的 ,ES11诞生了个新特性--空位合并操作符,用 ?? 表示。如果表达式在??的左侧运算符求值为 undefined 或 null,就返回其右侧默认值。
const x = null;
const y = x ?? 500;
console.log(y); // 500
const n = 0
const m = n ?? 9000;
console.log(m) // 0
import()动态导入
现代前端的打包资源越来越大,这些引入的资源越多,首屏加载的速度就会越慢,而前端应用初始化时并不需要全量的加载逻辑资源, 为了首屏渲染速度更快,就可以把这些进行按需导入.
一般按需加载的逻辑资源都会放在某一个事件的回调当中执行
el.onclick = () => {
import('/modules/my-module.js')
.then(module => {
// Do something with the module.
})
.catch(err => {
// load error;
})
}
//这种使用方式也支持 await 关键字。
let module = await import('/modules/my-module.js');
关键字import可以像调用函数一样来动态的导入模块。以这种方式调用,将返回一个 promise,使用export导出,import()导入
const add = (num1, num2) => num1 + num2;
export { add };
const doMath = async (num1, num2) => {
if (num1 && num2) {
const math = await import('./math.js');
console.log(math.add(5, 10));
};
};
doMath(4, 2);
与import声明相比,import()特点如下:
● 能够在函数、分支等非顶层作用域使用,按需加载、懒加载都不是问题
● 模块标识支持变量传入,可动态计算确定模块标识
● 不仅限于module,在普通的script中也能使用
注意,虽然长的像函数,但import()实际上是个操作符,因为操作符能够携带当前模块相关信息(用来解析模块表示),而函数不能
适用场合
1. 按需加载(比如点击时加载某个文件或者模块)
2. 条件加载(比如if判断模块中)
3. 动态的模块路径(比如模块路径是实时生成)
for-in遍历顺序
● ES11中,官方指定了for…in遍历顺序的规范。(之前各个浏览器引擎都有自己的for…in实现,所以对某些变量,例如对象的遍历顺序可能不一致)
● 以后,各个浏览器引擎都会遵循ECMA指定的遍历顺序,保证各个引擎的遍历结果一致。
使用
以一个对象为例, 会先遍历出整数属性,按照升序的顺序遍历,然后其他属性按照创建时候的顺序遍历出来。
let obj = {
x2: 'x2',
x1: 'x1',
'3': '3',
'1': '1',
'2': '2',
'3XX': '3XX',
};
for (let key in obj) {
console.log(obj[key]);
}
// 1
// 2
// 3
// x2
// x1
// 3XX
● 遍历不到 Symbol 类型的属性
● 遍历过程中,目标对象的属性能被删除,忽略掉尚未遍历到却已经被删掉的属性
● 遍历过程中,如果有新增属性,不保证新的属性能被当次遍历处理到
● 属性名不会重复出现(一个属性名最多出现一次)
● 目标对象整条原型链上的属性都能遍历到
String.prototype.matchAll
字符串处理的一个常见场景是想要匹配出字符串中的所有目标子串,
matchAll方法,可以一次性取出所有匹配 .
matchAll()与match()的区别
matchAll()不用正则也会匹配所有符合的字符串(如果使用正则,不能使用不带g修饰符的正则,否则会报错)
match()使用带g的正则表达式则才能可以匹配所有,否则只匹配第一个
在有多个匹配时matchAll()不像match() 一样返回数组,而是返回一个迭代器,可以使用for…of…,数组新增的扩展符(…)或Array.from()实现功能
let str = '<text>JS</text><text>正则</text>';
let allMatchs = str.matchAll(/<\w+>(.*?)</\w+>/g);
//console.log(allMatchs)
for (const match of allMatchs) {
console.log(match);
}
/*
第一次迭代返回:
[
"<text>JS</text>",
"JS",
index: 0,
input: "<text>JS</text><text>正则</text>",
groups: undefined
]
第二次迭代返回:
[
"<text>正则</text>",
"正则",
index: 15,
input: "<text>JS</text><text>正则</text>",
groups: undefined
]
*/
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"]
Promise.allSettled()
类似于all,但不会因为某些项rejected而短路,也就是说,allSettled会等到所有项都有结果(无论成功失败)后才进入Promise链的下一环(所以它一定会变成 fulfilled 状态)
Promise.allSettled([
Promise.reject({ code: 500, msg: '服务异常' }),
Promise.resolve({ code: 200, list: [] }),
Promise.resolve({ code: 200, list: [] })
]).then(res => {
console.log(res)
/*
0: {status: "rejected", reason: {…}}
1: {status: "fulfilled", value: {…}}
2: {status: "fulfilled", value: {…}}
*/
// 过滤掉 rejected 状态,尽可能多的保证页面区域数据渲染
RenderContent(
res.filter(el => {
return el.status !== 'rejected'
})
)
})
类私有域
在定义一个类时,是为了更好的抽象和复用,但类中有一些变量我们不希望外界可以直接修改或使用,我们更希望他是私有化的,那么可以在这些变量前添加“#”来达到私有化的目的。
class Message {
#aaa = "hello"
bbb() { console.log(this.#aaa) }
}
const greeting = new Message()
greeting.bbb() // hello
console.log(greeting.#aaa) // Private name #aaa is not defined
在外部直接访问会报错,只能通过内部的方法抛出
es2021(即es12)
弱引用:WeakRef
WeakRef 提案主要包含两个新功能:
● 可以通过 WeakRef 类来给某个对象创建一个弱引用
● 可以通过 FinalizationRegistry 类,在某个对象被垃圾回收之后,执行一些自定义方法
一般来说,在 JavaScript 中,对象的引用是强保留的,这意味着只要持有对象的引用,它就不会被垃圾回收。
es12有了新的weakRef,允许创建对象的弱引用,让对象在没有其他引用的情况下被垃圾回收机制回收,当你希望某个对象在没有其他引用时及时的被垃圾回收(避免内存泄漏),那么可以使用它 ,比如储存 DOM 节点,而不用担心这些节点被移除时,会引发内存泄漏。
必须使用new关键字创建新的WeakRef,并把对象作为参数放入括号内,当你想读取被引用的对象时,可以在弱引用对象上调用deref()方法来实现,下面是一个很简单的例子
const myWeakRef = new WeakRef({
name: 'Cache',
size: 'unlimited'
})
console.log(myWeakRef.deref())
// Output:
// { name: 'Cache', size: 'unlimited' }
console.log(myWeakRef.deref().name)
// Output:
// 'Cache'
console.log(myWeakRef.deref().size)
// Output:
// 'unlimited'
// 如果 引用的对象 被垃圾回收了,则 myWeakRef 就会是 undefined
官方建议尽量避免使用WeakRef,不同引擎的GC回收逻辑不同,使用不当很可能出错
FinalizationRegistry(终结器)
1. 第一个参数:要为其注册 终结器 的对象。
2. 第二个参数:上面定义的回调函数的值。
const a = {};
const obj = new WeakRef(a);
const fina = new FinalizationRegistry((v) => {
console.log(v);
});
fina.register(a, "a被回收了");
fina.obj.deref(); //a
console.log(a);
obj.deref(); //undefined 如果a被回收了
Promise.any
Promise.any 也接受一个 Promise 的数组。当其中任何一个 Promise 完成时,就返回那个已经成功的 promise。如果所有的 Promise 都拒绝(reject),则返回一个拒绝的 Promise,该 Promise 的返回值是一个 AggregateError 对象。可以看成promise.all的相反操作。
● Promise.all() :任意一个 promise 被 reject ,就会立即被 reject ,并且 reject 的是第一个抛出的错误信息,只有所有的 promise 都 resolve 时才会 resolve 所有的结果
● Promise.any() :任意一个 promise 被 resolve ,就会立即被 resolve ,并且 resolve 的是第一个正确结果,只有所有的 promise 都 reject 时才会 reject 所有的失败信息
当我们只需要一个promise成功,而不关心是哪一个成功时此方法很有用的,如果你有多台服务器,则尽量使用响应速度最快的服务器,在这种情况下,可以使用 Promise.any() 方法从最快的服务器接收响应
const promise1 = new Promise((resolve, reject) => {
reject('我promise1失败了');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, '我promise2成功了');
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, '我promise3成功了');
});
Promise.any([promise1, promise2, promise3]).then((value) => {
console.log(value); // 我promise3成功了
});
如果没有成功的 promise,Promise.any()返回AggregateError错误:
const promise1 = new Promise((resolve, reject) => {
reject('我promise1失败了');
});
const promise2 = new Promise((resolve, reject) => {
reject('我promise2失败了');
});
const promise3 = new Promise((resolve, reject) => {
reject('我promise3失败了');
});
Promise.any([promise1, promise2, promise3]).then((value) => {
console.log(value); // Uncaught (in promise) AggregateError: All promises were rejected
});
String.prototype.replaceAll
我们之前需要替换一个字符串一般会使用replace()方法,使用replace()方法后,我们常见的一个问题就是replace()不能替换所有指定的字符串。因为它仅替换第一次出现的子字符串。
'abbc'.replace('b', '_');
// 输出则是: "a_bc"
//我们可以看到仅替换了第一个字符串。那么,对于开发人员通常会使用带有global(g)标志的正则表达式进行处理
'abbc'.replace(/b/g, '_');
// 输出则是: "a__c"
另外一种方法就是使用split()结合join()方法处理。
// 如:将中间的加号“+”替换为空。
's=a+b+c'.split('+').join(' ');
// 则输出:"s=a b c"
这两种方法都可以替换所有,但是都有些许的麻烦,es2021新出的replaceAll()则可以一次性全部替换,为了与现有API保持一致,其replaceAll()处理方式与replace()完全相同。
因此,replaceAll通过提供对全局字符串替换,而无需使用正则表达式或其他解决方法,如果需要使用正则表达式的话则必须设置全局(“ g”)标志;否则,它将抛出TypeError:“必须使用全局RegExp调用replaceAll”。
's=a+b+c'.replaceAll('+', ' ');
// => "s=a b c"
逻辑赋值操作符
逻辑赋值运算符结合了逻辑运算(&&,||或??)
我们知道,下面的代码:
let a = 0;
a = a + 2;
可以简写为
let a = 0;
a += 2;
那么这个新的标准中,逻辑表达式的操作符(&&、||、??)也可以和上面一样简写了
//如果a为真,则返回a;如果a为假,则返回b
a ||= b; // 等同于 a = a || b
//如果a为真或a为假,则a &&= b返回b
a &&= b; // 等同于 c = c && d
//如果a为null或undefined,则返回b;如果a为真,则返回a
a ??= b; // 等同于 e = e ?? f
let a = 1,
b = 2,
c = 0,
d = undefined;
a &&= b;
b ||= c;
d ??= c;
console.log(a, b, d); // 2 2 0
数字分隔符
通过这个功能,我们利用 “(_,U+005F)” 分隔符来将数字分组,提高数字的可读性:
let x = 2_3333_3333
// x 的值等同于 233333333,只是这样可读性更强,不用一位一位数了
Intl.ListFormat
Intl.ListFormat 是一个构造函数,用来处理和多语言相关的对象格式化操作。ListFormat 对象的构造方法有两个参数,皆为可选。首个参数是一个语言标识,而第二个参数是一个选项对象 -- 包含了 style 和 type 两个属性
const arr = ['Pen', 'Pencil', 'Paper']
let obj = new Intl.ListFormat('en', { style: 'short', type: 'conjunction' })
console.log(obj.format(arr))
/**** 输出 ****/
// Pen, Pencil, & Paper
obj = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' })
console.log(obj.format(arr))
/**** 输出 ****/
// Pen, Pencil, and Paper
obj = new Intl.ListFormat('en', { style: 'narrow', type: 'conjunction' })
console.log(obj.format(arr))
/**** 输出 ****/
// Pen, Pencil, Paper
// 传入意大利语标识
obj = new Intl.ListFormat('it', { style: 'short', type: 'conjunction' })
console.log(obj.format(arr))
/**** 输出 ****/
// Pen, Pencil e Paper
// 传入德语标识
obj = new Intl.ListFormat('de', { style: 'long', type: 'conjunction' })
console.log(obj.format(arr))
/**** 输出 ****/
// Pen, Pencil und Paper
Intl.DateTimeFormat API 中的 dateStyle 和 timeStyle 的配置项
Intl.DateTimeFormat 对象是一个支持语言敏感日期和时间格式化的构造器。拟议的 dateStyle 和 timeStyle 选项可被用于获取一个 locale 特有的日期和给定长度的时间
let a = new Intl.DateTimeFormat("en" , {
timeStyle: "short"
});
console.log(a.format(Date.now())); // "13:31"
let b = new Intl.DateTimeFormat("en" , {
dateStyle: "short"
});
console.log(b.format(Date.now())); // "21.03.2012"
// 可以通过同时传入 timeStyle 和 dateStyle 这两个参数来获取更完整的格式化时间的字符串
let c = new Intl.DateTimeFormat("en" , {
timeStyle: "medium",
dateStyle: "short"
});
console.log(c.format(Date.now())); // "21.03.2012, 13:31"