🔥「实例」ES2021来了,Blue带你看看有哪些值得了解的

我心飞翔 分类:javascript

OIP.bmp

ES12总览

ES2021(简称ES12)会在2021年中发布,那么,有什么值得期待的新特性,Blue带你了解一下

其实很多特性我们早就在用了,只是到ES12才正式定稿罢了,所以大家会见到一些熟面孔

根据官方的说法,有几个已经确定的特性,我们来有个大概的印象

  • 字符串的replaceAll方法
  • 数字分隔符
  • 逻辑赋值运算符
  • Promise.any
  • WeakRefs弱引用
  • FinalizationRegistry弱引用回收监听
  • Intl本地化字符串

大概有感觉了,那我们一个个来说

备注:下文“实用度”具有较大的个人主观色彩,仅供参考——用到哪个,哪个就是100%的实用

ES12新特性详解

1-字符串的replaceAll方法

实用度:★★★★☆

我们知道字符串的replace默认只能替换一次,如果想要全部替换,必须搭配正则使用,而replaceAll就是给了我们另一种选择,直接上代码

const str='我是blue,这里好多abc abc的';

//1-普通replace
str.replace('b', '*');  //'我是*lue,这里好多abc abc的'

//2-传统的解决方法,正则+g
str.replace(/b/g, '*'); //'我是*lue,这里好多a*c a*c的'

//3-新方法-replaceAll
str.replaceAll('b', '*');//'我是*lue,这里好多a*c a*c的'
 

乍看之下这玩意好像没啥用啊,我正则不是挺好吗,其实它还有个隐藏的功能,可以用一下

const str='我是blue,这里好多abc abc的';

//replaceAll也可以用于正则
str.replaceAll(/b/g, '*');  //'我是*lue,这里好多a*c a*c的'

//而且如果没有g,会报错
str.replaceAll(/b/, '*');  //报错"replaceAll called with a non-global RegExp argument"
 

这个特性初看感觉是自讨没趣,“我跟自己有仇吗?我用replace多好,还不报错”,其实不是:

  • 用replace,相当于把选择权交给正则,正则有没有g选项,会直接干扰我的结果(更新1个还是全部)
  • 用replaceAll,则相当于强制替换所有,代码的行为是固定的,帮助我们排查问题

典型应用

提升代码健壮性、利于调试

如果你的本意就是想替换所有匹配项,那么使用replaceAll是个好办法,防止因为正则写错、传错导致程序的行为偏离目标,而且还能替你排查代码问题

2-数字分隔符

实用度:★★★★☆

可以将数字中分隔成几部分,提升可读性,类似于千分位

let a=123456;
let b=12_345_6;

console.log(a===b);  //true
 

这个特性唯一的用处就是增强可读性,尤其是比较大的数字数零都数到眼花,就很实用

const maxCount=1000000;  //一顿数,个、十、百...、数到几位来着?

//这样就很清晰
const maxCount=1_000_000; //1兆(不是1M)
const maxCount=100_0000;  //100万
 

典型应用

用于增强大数字的可读性

3-逻辑赋值运算符

实用度:★★☆☆☆

js里能简写很多种赋值(+=、-=、%=啥的),从ES12开始,逻辑运算也能简写了

//等价
a = a&&b;
a &&= b;

//等价
a = a||b;
a ||= b;

//等价
a = a??b;
a ??= b;
 

&&||比较普通相信大家熟,??可能有人没见过,简单解释一下——??判断是否为空(null或undefined)

c=a ?? b;
//等价于
if(a!==null && a!==undefined){
  c=a;
}else{
  c=b;
}
 

??用起来还挺方便的,因为它只对null和undefined这种空值起作用,其他假值没反应(比如:0、""、false之类的)

简单总结一下

  • a||=b:如果a为真,则a=a;否则a=b
  • a&&=b:如果a为真,则a=b;否则a=a
  • a??=b:如果a为空,则a=b;否则a=a

典型应用

任何逻辑运算与赋值同时进行时,都可以使用

//假设:校验用户权限
let isOK=true;

isOK &&= logined;
isOK &&= user.paied;
isOK &&= count>0;  //其实直接连起来写也差不多。。。(小声)

if(isOK){
  await db.query(...);
}else{
  throw new Error('invalid user state');
}
 

4-Promise.any

实用度:★★★★☆

同时执行多个异步任务(也就是promise实例),如果有一个成功了(resolved)则返回成功,如果所有都失败了,则报错(rejected),这里有个重点:

  • 任意一个成功了,就算作成功;如果都失败了,才算作失败(重点,马上会看到为什么
//实例1:都成功,时间不同
Promise.any([
  //任务1:0.5秒后返回5
  new Promise((resolve, reject)=>{
    setTimeout(()=>resolve(5), 500);
  }),
  //任务2:1秒后返回12
  new Promise((resolve, reject)=>{
    setTimeout(()=>resolve(12), 1000);
  }),
]).then(console.log);

//结果是5,因为5先返回
 
//实例2:有成功有失败
Promise.any([
  //任务1:0.5秒后失败
  new Promise((resolve, reject)=>{
    setTimeout(()=>reject(), 500);
  }),
  //任务2:1秒后返回12
  new Promise((resolve, reject)=>{
    setTimeout(()=>resolve(12), 1000);
  }),
]).then(console.log);

//结果是12,任务1失败后,继续等待直到12返回
 

和race的区别

看了上面的介绍和例子,大家一定觉得——这跟Promise.race不是一样吗?搞个any出来干啥

其实any和race区别还挺明显的(为了加深印象,请回顾上面说的重点

  • race:纯看哪个快,不论对错

    race以第一个返回的为准,如果最快的那个报错了,那就整体算错,返回失败

  • any:一直等到有正确的为止

    any以第一个成功的为准,如果最快的那个报错了,就等下一个返回,如果全都错了才返回错误

来直接看个代码吧

//为了突出对比,将忽略部分代码

Promise.race([
  reject(1, 500ms),
  resolve(2, 1000ms)
])  //在500ms时,reject结束——因为任务1更快,它的对错决定整体结果

Promise.any([
  reject(1, 500ms),
  resolve(2, 1000ms)
])  //在1000ms时,resolve结果2——因为任务1错了之后,会继续等待,直到任务2返回
 

典型应用

对于需要从多个源返回结果的操作非常有用

//假设从三个不同地区的源获取数据——用于判别用户用哪个源更快
Promise.any([
  request('//bj.zhinengshe.com/1.json'),
  request('//sh.zhinengshe.com/1.json'),
  request('//sz.zhinengshe.com/1.json'),
]).then(res=>{
  if(res.name=='bj'){
    //后续以bj线交互
  }else if(res.name=='sh'){
    ...
  }else{...}
});

//备注:仅作为实例使用,现在智能多线和cdn这么发达,压根不用这么折腾
 

5-WeakRefs弱引用

实用度:★★★★★

弱引用很常用,但以前我们只有WeakMapWeakSet,而WeakRefs允许我们创建任意对象的弱引用

什么是弱引用

我们知道,JS和java一样带有GC垃圾回收机制,而且也都是通过引用计数来确定是否需要回收的,所以强引用(例如变量)会让数据一直存在,而弱引用本身不增加引用计数,当对象所有的强引用消失仅剩弱引用时,可以被回收

//普通Map
let obj={name: 'blue', org: 'zhinengshe'};  //引用计数:1 (obj变量)
let map=new Map();

map.set(obj, 100);  //引用计数:2  (obj、map)

obj=null;  //不会回收
 
//WeakMap
let obj={name: 'blue', org: 'zhinengshe'};  //引用计数:1 (obj变量)
let map=new WeakMap();

map.set(obj, 100);  //引用计数:1  (obj变量)

obj=null;  //回收
 

典型应用

需要存储对象,但可以随时回收时(例如:缓存)——对大型数据对象(如图片、音视频等)尤其好用

//方法1——不带缓存
export async function loadImage(name){
  //这里的逻辑不重要,就是读个图片过来
  return await request(imgRoot+name);
}

//方法2——带有缓存的
//重点在这里
const imgCache=new Map();
export async function loadImageCached(name){
  let val=imgCache.get(name);

  //检查缓存
  if(val){
    const img=val.deref();
    if(img)return img;
  }

  //没有缓存,重新读
  const img=await loadImage(name);

  /*************************
        重点来了
  *************************/
  imgCache.set(name, new WeakRef(img));

  return img;
}
 

重点在于new WeakRef(img),它可以创建一个弱引用对象,随时可以回收,不占用引用计数

6-FinalizationRegistry弱引用回收监听

实用度:★★☆☆☆

这个特性是和上面的WeakRefs关联的,可以用于监听弱引用回收的通知,不过我们大部分应用是不会依赖弱引用回收的——既然你已经是弱引用了,就不在乎人家啥时候给你删了;如果你很在乎,那就用强引用多好

所以,这个特性的实用性不高,对绝大多数应用没有作用

//回收通知对象
const registry=new FinalizationRegistry((value)=>{
  console.log('对象被回收了', value);  //这里的value是下面注册时传入的
});

(()=>{
  //构建一个对象,它回收时我们希望有回调
  const obj={name: 'blue'};
  
  registry.register(obj, '这里的值,会作为value传给callback');
})();

//函数结束后,obj被回收时可以看到——"对象被回收了" "这里的值,会作为value传给callback"
//备注:回收动作可能立即触发,也可能很久才触发,不可预测
 

典型应用

当需要监听一个对象的回收动作时触发

7-Intl本地化字符串

实用度:★★★☆☆

可以帮我们快速输出本地化的日期、字符串等,如果对输出格式没有很高的要求还是很实用的

const l=['a','b','c'];

//按英文输出
new Intl.ListFormat('en-GB').format(l);
//"a, b and c"

//按中文输出
new Intl.ListFormat('zh').format(l)
//"a、b和c"


//和
new Intl.ListFormat('en-GB', {type: 'conjunction'}).format(l)
//"a, b and c"

//或
new Intl.ListFormat('en-GB', {type: 'disjunction'}).format(l)
//"a, b or c"
 

也可以处理日期时间

//英文格式
new Intl.DateTimeFormat('en', {}).format(Date.now())
//"3/10/2023"

//中文格式
new Intl.DateTimeFormat('zh', {}).format(Date.now())
//"2023/3/10"
 

典型应用

对于格式要求不高的场景,快速实现数据的本地化(自带的吐槽:说着简单,但i18n其实一堆事,光靠它一个不够的)

if(navigator.language=='en-US'){
  toast(
    `Thanks for signing up zhinengshe's course, your course will start at: `+
    new Intl.DateTimeFormat('en', {}).format(courseTime)
  );
}else{
  toast(
    '感谢报名智能社的课程,课程将开始于:'+
    new Intl.DateTimeFormat('zh', {}).format(courseTime)
  );
}
 

总结

是时候梳理一遍Blue讲过的东西了,那么首先

15-三连

  • replaceAll:**【推荐】**强制替换全部匹配项,需要替换全部时应优先使用,有助于找出程序错误(比如:正则没写g)
  • 数字分隔符:用于增强长数字的可读性
  • 逻辑赋值运算符:就是个简写,赋值和逻辑运算(&&、||、??)碰到一起就可以用
  • Promise.any:**【推荐】**同时执行多个操作,任意操作成功或全部失败时执行,比如race更实用
  • WeakRefs:**【推荐】**缓存等弱引用场景很实用
  • FinalizationRegistry:除非你的程序依赖于GC回收机制(例如:编写开发工具),否则用处并不大

有bug?想补充?

感谢大家观看这篇教程,有任何问题或想和我交流,请直接留言,发现文章有任何不妥之处,也请指出,提前感谢

回复

我来回复
  • 暂无回复内容