• ECMAScript 版本新特性抢先看
  • 发布于 2个月前
  • 224 热度
    0 评论
不得不说,前端更新迭代地太快了,不止是各种各样的轮子、框架,JS 本身也在持续发布新语法。这篇文章就分享下预计今年发布的 ECMAScript 版本的一些新特性。大家可以当个收藏文~

数组查找
在数组中查找元素是非常见的需求,但是目前 ECMAScript 只支持 indexOf 和 lastIndexOf 两个数组查找方法。如果我们想要查找满足条件的最后一个元素时,就需要使用 reverse 方法:
// 堆代码 duidaima.com
const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];
array.find(n => n.value % 2 === 1); // { value: 1 }
array.findIndex(n => n.value % 2 === 1); // 0

// 使用 reverse 和 find 实现查找
[...array].reverse().find(n => n.value % 2 === 1); // { value: 3 }
array.length - 1 - [...array].reverse().findIndex(n => n.value % 2 === 1); // 2
但是使用这种方法存在一些问题,如不必要的突变和复制,以及复杂的索引计算。所以,为了解决这个问题引入了一项新的提案,目的是提供一种更清晰地表示查找操作的方法。还可以提高性能,避免不必要的开销。虽然性能提升不一定非常的显著,但在某些性能敏感的场景下可能会非常有用,比如 React 渲染函数里面。

这个提案的核心功能就是在数组中通过条件函数从后往前查找元素。新的方法名为 {Array, %TypedArray%}.prototype.findLast 和 {Array, %TypedArray%}.prototype.findLastIndex,它们的行为就类似于 Array.prototype.find 和 Array.prototype.findIndex,但是是从后往前查找的。这样一来,就可以避免不必要的突变和复制,同时也可以减少索引计算的复杂度:
// 使用新的 findLast 和 findLastIndex 实现查找
array.findLast(n => n.value % 2 === 1); // { value: 3 }
array.findLastIndex(n => n.value % 2 === 1); // 2

Hashbang Grammar
Hashbang Grammar 提案主要是为了规范在 CLI 中执行 JS 脚本的 shebangs 的使用。所谓 shebangs,就是在文件开头的一行,以 #! 开头的注释,用来指定脚本的解释器。举个例子,比如在 Unix/Linux 系统中,我们可以在脚本的第一行写上:
#!/usr/bin/env node
console.log('你好呀 17');
这个注释的意思是,使用 Node.js 作为解释器来运行这个脚本。现有的 JS CLI  脚本通常会去掉 hashbangs,然后再把剩下的代码传给 JS 引擎去执行。Hashbang Grammar 提案主要就是建议把去掉 hashbangs 的工作移到引擎中去做,以此来统一和规范化操作的方式。

WeakMap 的 Symbols key
目前,WeakMaps 仅允许使用普通对象作为 key,这是一种限制,主要是因为目标是拥有可以最终进行垃圾回收的唯一值。Symbol 是 ECMAScript 中唯一允许唯一值的原始类型。像使用 Symbol([description]) 表达式调用的时候产生的值只能通过访问它的原始输出来识别。任何其他相同的表达式都不会恢复最开始生产的原始值。那么,使用 Symbol 作为 key 主要是因为下面两个好处吧。

第一个是使用 Symbol 作为 WeakMap 的 key 可以更清晰地表明它的键和映射项的角色关系,而不需要创建一个只用作键的新对象。
const weak = new WeakMap();
const key = Symbol("ref");
weak.set(key, "Hi Hi Hi 17");

console.log(weak.get(key));
在之前的文章中我们讲过 ES 中有一个新的关于沙箱的提案 ShadowRealms:

比 eval 和 iframe 更强的新一代 JavaScript 沙箱
ShadowRealms 方案会禁止访问一个对象的值。大多数虚拟化环境情况下,会在基于 Realms 相关 API 构建一个 Membranes 系统,然后使用 WeakMaps 连接引用。由于 Symbol 值是原始值,仍然是可以访问的,在这个场景中是非常实用的:
// 堆代码 duidaima.com
const objectLookup = new WeakMap();
const otherRealm = new ShadowRealm();
const coinFlip = otherRealm.evaluate(`(a, b) => Math.random() > 0.5 ? a : b;`);

// later...
let a = { name: 'alice' };
let b = { name: 'bob' };
let symbolA = Symbol();
let symbolB = Symbol();
objectLookup.set(symbolA, a);
objectLookup.set(symbolB, b);
a = b = null; // ok to drop direct object references

// connected identities preserved as the symbols round-tripped through the other realm
let chosen = objectLookup.get(coinFlip(symbolA, symbolB));
assert(['alice', 'bob'].includes(chosen.name));
Change Array by copy
为啥这个提案叫 Change Array by copy 呢?字面意思就是从副本里改变数组。这就要说起数组的破坏性和非破坏性方法了:
有些数组的方法我们在调用的时候不会改变原始的数组,我们称它们为非破坏性方法,比如我们经常用到的 filter、some、map、find 等方法,斗是不会改变原数组的:

但是,另外有一些方法是会改变原数组本身的,比如:sort、reverse、splice 等方法。

可以看到,原数组和排序后得到的新数组是一样的,说明这个方法改变了原数组。很多时候我们想用这些方法,但是又不想改变原数组,我们可能会先创建一个副本,比如下面这些操作:
const sorted1 = array1.slice().sort();
const sorted2 = [...array1].sort();
const sorted3 = Array.from(array1).sort();
几个数组的新方法,就是用来解决这样的问题的。

toSorted()
.toSorted() 是 .sort() 的非破坏性版本:
const array = ['c', 'o', 'n', 'a', 'r', 'd', 'l', 'i'];
const result = array.toSorted();
console.log(result); //  ['a', 'c', 'd', 'i', 'l', 'n', 'o', 'r']
console.log(array); // ['c', 'o', 'n', 'a', 'r', 'd', 'l', 'i']
下面是个简单的 polyfill:
if (!Array.prototype.toSorted) {
  Array.prototype.toSorted = function (compareFn) {
    return this.slice().sort(compareFn);
  };
}
toReversed()
.toReversed() 是 .reverse() 的非破坏性版本:
const array = ['c', 'o', 'n', 'a', 'r', 'd', 'l', 'i'];
const result = array.toReversed();
console.log(result); //  ['i', 'l', 'd', 'r', 'a', 'n', 'o', 'c']
console.log(array); // ['c', 'o', 'n', 'a', 'r', 'd', 'l', 'i']
下面是个简单的 polyfill:
if (!Array.prototype.toReversed) {
  Array.prototype.toReversed = function () {
    return this.slice().reverse();
  };
}

with()
with() 是对数组的某个元素赋值操作的非破坏性版本,比如下面的操作:
array[index] = value
如果我们只是想得到一个新数组,又不想改变原数组,可以这样用:
const array = ['c', 'o', 'n', 'a', 'r', 'd', 'l', 'i'];
const result = array.with(0, 'ConardLi')
console.log(result); //  ['ConardLi', 'o', 'n', 'a', 'r', 'd', 'l', 'i'];
console.log(array); // ['c', 'o', 'n', 'a', 'r', 'd', 'l', 'i']
下面是个简单的 polyfill:
if (!Array.prototype.with) {
  Array.prototype.with = function (index, value) {
    const copy = this.slice();
    copy[index] = value;
    return copy;
  };
}
toSpliced()
.splice(start, deleteCount, ...items) 方法比其他几个破坏性方法更复杂点:
1.它从 start 开始删除 deleteCount 个元素 ;
2.然后把 items 插入到被删除的位置;
3.最后返回已删除的元素。
const array = [1, 2, 3, 4, 5, 6];
const result = array.splice(1, 2, 0);
console.log(result); //  [2, 3]
console.log(array);  // [1, 0, 4, 5, 6]
.tospliced() 是 .splice() 的非破坏性版本,它会返回原数组变更后的版本,因此我们拿不到被删除的元素:
const array = [1, 2, 3, 4, 5, 6];
const result = array.tospliced(1, 2, 0);
console.log(result); //  [1, 0, 4, 5, 6]
console.log(array);  // [1, 2, 3, 4, 5, 6]
下面是个简单的 polyfill:
if (!Array.prototype.toSpliced) {
  Array.prototype.toSpliced = function (start, deleteCount, ...items) {
    const copy = this.slice();
    copy.splice(start, deleteCount, ...items);
    return copy;
  };
}
最后
大家感觉哪个最有用?
用户评论