前端又要出新语法了。。
The following article is from code秘密花园 Author ConardLi
大家好,我是鱼皮。不得不说,前端更新迭代地太快了,不止是各种各样的轮子、框架,JS 本身也在持续发布新语法。
这篇文章就分享下预计今年发布的 ECMAScript
版本的一些新特性。大家可以当个收藏文~
数组查找
在数组中查找元素是非常见的需求,但是目前 ECMAScript
只支持 indexOf
和 lastIndexOf
两个数组查找方法。
如果我们想要查找满足条件的最后一个元素时,就需要使用 reverse
方法:
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
值是原始值,仍然是可以访问的,在这个场景中是非常实用的:
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)
方法比其他几个破坏性方法更复杂点:
它从 start 开始删除 deleteCount 个元素 ; 然后把 items 插入到被删除的位置; 最后返回已删除的元素。
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;
};
}
最后
大家感觉哪个最有用?
往期推荐