JavaScript:明年对我们来说下一步是什么





朋友们,美好的一天!



本文重点介绍将在规范的新版本(ECMAScript 2021,ES12)中提供的JavaScript功能



关于以下内容:



  • String.prototype.replaceAll()
  • Promise.any()
  • 弱引用
  • 布尔赋值运算符
  • 数字分隔符




String.prototype.replaceAll()



String.prototype.replaceAll()(Mathias Bynens'子句)允许您使用不同的值替换字符串中子字符串的所有实例,而无需使用全局正则表达式。



在下面的示例中,我们使用正则表达式将所有“ +”字符替换为逗号并带有空格:



const strWithPlus = '++'
const strWithComma = strWithPlus.replace(/+/g, ', ')
// , , 


这种方法需要使用正则表达式。但是,复杂的正则表达式通常是错误的来源。



还有另一种基于使用String.prototype.split()和Array.prototype.join()方法的方法:



const strWithPlus = '++'
const strWithComma = strWithPlus.split('+').join(', ')
// , , 


这种方法避免使用正则表达式,但是您必须将字符串拆分为单独的部分(单词),将其转换为数组,然后将数组元素连接为新的字符串。



String.prototype.replaceAll()解决了这些问题,并提供了一种简单方便的方式来全局替换子字符串:



const strWithPlus = '++'
const strWithComma = strWithPlus.replaceAll('+', ', ')
// , , 


请注意,为了与以前的API保持一致,String.prototype.replaceAll(搜索值,新值)(搜索值是搜索值,新值是新值)的行为与String.prototype.replace(搜索值,新值)相同​​,但以下情况除外:



  • 如果搜索的值是字符串,则replaceAll替换所有匹配项,并且仅替换第一个
  • 如果所需值是非全局正则表达式,则replace替换第一个匹配项,replaceAll引发异常,以避免缺少“ g”标志和方法名称之间的冲突(全部替换-替换所有[matches])


如果将全局正则表达式用作查找值,那么replace和replaceAll的行为相同。



如果我们在行的开头,结尾和单词之间有任意数量的空格怎么办?



const whiteSpaceHell = '          '


我们想用一个替换两个或多个空格。replaceAll可以解决这个问题吗?没有。



使用String.prototype.trim()并替换为全局正则表达式,可以像这样完成:



const whiteSpaceNormal =
  whiteSpaceHell
    .trim()
    .replace(/\s{2,}/g, ' ')
    // \s{2,}     
    //   


Promise.any()



Promise.any()(由Mathias Bynens,Kevin Gibbons和Sergey Rubanov提出的建议)将返回第一个已兑现承诺的价值。如果作为参数(作为数组)传递给Promise.any()的所有Promise均被拒绝,则会引发“ AggregateError”异常。



AggregateError是一个新的Error子类,将各个错误分组。每个AggregateError实例都包含对带有异常的数组的引用。



让我们考虑一个例子:



const promise1 = new Promise((resolve, reject) => {
  const timer = setTimeout(() => {
    resolve('p1')
    clearTimeout(timer)
  }, ~~(Math.random() * 100))
}) // ~~ -   Math.floor()

const promise2 = new Promise((resolve, reject) => {
  const timer = setTimeout(() => {
    resolve('p2')
    clearTimeout(timer)
  }, ~~(Math.random() * 100))
})

;(async() => {
  const result = await Promise.any([promise1, promise2])
  console.log(result) // p1  p2
})()


结果将是第一个解决的承诺的价值。



句子中的示例:



Promise.any([
  fetch('https://v8.dev/').then(() => 'home'),
  fetch('https://v8.dev/blog').then(() => 'blog'),
  fetch('https://v8.dev/docs').then(() => 'docs')
]).then((first) => {
  //  ()   
  console.log(first);
  // → 'home'
}).catch((error) => {
  //    
  console.log(error);
})




请注意,Promise.race()与Promise.any()不同,它返回第一个已解决的Promise的值,无论是实现还是拒绝。



弱引用



WeakRefs(弱引用)(由Dean Tribble,Mark Miller,Till Schneidereit等提出)提供了两个新功能:



  • 使用WeakRef类创建对对象的弱引用
  • 使用FinalizationRegistry类在垃圾回收之后运行自定义终结器


简而言之,WeakRef允许您创建对象的弱引用,这些对象是另一个对象的属性值,除其他外,终结器可以用于删除对由垃圾收集器``清理''的对象的引用。



当创建使用内置缓存的记忆功能(记忆)以防止重复执行该功能时,如果在缓存中传递给该函数的参数有计算值(如果对象被用作缓存对象的属性值以及随后被删除的风险),此技术将非常有用...



您还记得,在JavaScript中出现诸如Map(哈希表)之类的结构的原因,除了可以通过键快速搜索值之外,还在于普通对象的键只能是字符串或字符。另一方面,Map允许您使用任何数据类型作为键,包括对象。



但是,内存泄漏问题很快就出现了:删除作为Map键的对象并不会使其无法访问(标记和清除),这阻止了垃圾收集器破坏它们,释放了它们所占用的内存。



换句话说,在地图中用作键的对象将被永久保存。



引入了另一个结构WeakMap(和WeakSet)来解决此问题。WeakMap和Map之间的区别在于,WeakMap中对关键对象的引用很弱:删除此类对象可使垃圾回收器重新分配为其分配的内存。



因此,该建议代表了JavaScript哈希表开发的下一阶段。对象现在可以用作其他对象中的键和值,而没有内存泄漏的风险。



再一次,谈到构建内联缓存:



  • 如果没有内存泄漏的风险,请使用Map
  • 使用随后可以删除的关键对象时,请使用WeakMap
  • 使用随后可以删除的值对象时,将Map与WeakRef结合使用


提案中最后一个案例的示例:



function makeWeakCached(f) {
  const cache = new Map()
  return key => {
    const ref = cache.get(key)
    if (ref) {
      //     
      const cached = ref.deref()
      if (cached !== undefined) return cached;
    }

    const fresh = f(key)
    //    ( )
    cache.set(key, new WeakRef(fresh))
    return fresh
  };
}

const getImageCached = makeWeakCached(getImage);


  • WeakRef构造函数接受必须为对象的参数,并返回对其的弱引用
  • WeakRef实例的deref方法返回两个值之一:


在内置缓存的情况下,终结器设计为在垃圾回收器破坏值对象后完成清理过程,或更简单地说,删除对此类对象的弱引用。



function makeWeakCached(f) {
  const cache = new Map()
  //    -   
  const cleanup = new FinalizationRegistry(key => {
    const ref = cache.get(key)
    if (ref && !ref.deref()) cache.delete(key)
  })

  return key => {
    const ref = cache.get(key)
    if (ref) {
      const cached = ref.deref()
      if (cached !== undefined) return cached
    }

    const fresh = f(key)
    cache.set(key, new WeakRef(fresh))
    //      ( )
    cleanup.register(fresh, key)
    return fresh
  }
}

const getImageCached = makeWeakCached(getImage);


阅读有关定稿器以及如何在提案中使用它们的更多信息。通常,仅在绝对必要时才使用终结器。



布尔赋值运算符



布尔运算符(贾斯汀·里奇韦尔的建议和Hemanth HM)是布尔运算符(&&,||,??)和赋值表达式的组合。



到目前为止,JavaScript具有以下赋值运算符:



=
 

+=
  

-=
  

/=
  

*=
  

&&=
   

||=
   

??=
      (null  undefined -  , 0, false,  '' -  )

**=
    

%=
    

&=
   

|=
   

^=
    

<<=
    

>>=
    

>>>=
       

  
[a, b] = [ 10, 20 ]
{a, b} = { a: 10, b: 20 }


该子句允许您组合逻辑运算符和赋值表达式:



a ||= b
// : a || (a = b)
//     ,   "a"  

a &&= b
// : a && (a = b)
//     ,   "a"  

a ??= b
// : a ?? (a = b)
//     ,   "a"   (null  undefined)


句子中的示例:



//    
function example(opts) {
  //  ,    
  opts.foo = opts.foo ?? 'bar'

  //   ,     
  opts.baz ?? (opts.baz = 'qux')
}

example({ foo: 'foo' })

//    
function example(opts) {
  //     
  opts.foo ??= 'bar'

  //  ""   opts.baz
  opts.baz ??= 'qux';
}

example({ foo: 'foo' })


数字分隔符



数字分隔符(Christophe Porteneuve的建议)或更确切地说是数字中的数字分隔符,允许您在数字之间添加下划线(_),以使数字更易读。



例如:



const num = 100000000
//     num? 1 ? 100 ? 10 ?


分隔符可解决此问题:



const num = 100_000_000 //  : 100 


分隔符可用于数字的整数和小数部分:



const num = 1_000_000.123_456


分隔符不仅可以用于整数和浮点数,还可以用于二进制,十六进制,八进制和BigInt文字。



数字分隔符的进一步发展意味着有可能在数字前后使用多个顺序分隔符和分隔符。



想要测试或提高您的JavaScript知识?然后注意我的精彩应用程序(您不能称赞自己...)。



希望您发现自己感兴趣的东西。感谢您的关注。



All Articles