ES14(ES2023)新特性

3 阅读4分钟

发布时间:2023年6月 ES14 新增了不可变数组方法、Hashbang 语法、从后查找数组等特性。


1. 不可变数组方法

ES14 为数组新增了4个方法,它们会返回新数组而不修改原数组(函数式编程风格):

Array.prototype.toReversed()

返回反转后的新数组:

let arr = [1, 2, 3, 4, 5];

// 旧方式:reverse() 会修改原数组
let copy = [...arr].reverse();  // [5,4,3,2,1]

// 新方式:toReversed() 不修改原数组
let reversed = arr.toReversed();  // [5,4,3,2,1]
console.log(arr);  // [1,2,3,4,5](原数组不变)

Array.prototype.toSorted()

返回排序后的新数组:

let arr = [3, 1, 4, 1, 5];

// 旧方式:sort() 会修改原数组
let copy = [...arr].sort((a, b) => a - b);

// 新方式:toSorted() 不修改原数组
let sorted = arr.toSorted((a, b) => a - b);  // [1,1,3,4,5]
console.log(arr);  // [3,1,4,1,5](原数组不变)

实际应用

// 按对象属性排序
let users = [
  { name: '张三', age: 30 },
  { name: '李四', age: 20 },
  { name: '王五', age: 25 }
];

let byAge = users.toSorted((a, b) => a.age - b.age);
// { name: '李四', age: 20 }, { name: '王五', age: 25 }, { name: '张三', age: 30 }

let byName = users.toSorted((a, b) => a.name.localeCompare(b.name));

Array.prototype.toSpliced()

返回删除/插入/替换后的新数组:

let arr = [1, 2, 3, 4, 5];

// 旧方式:splice() 会修改原数组
let copy = [...arr];
copy.splice(2, 1, 30, 40);  // [1,2,30,40,4,5]

// 新方式:toSpliced() 不修改原数组
let spliced = arr.toSpliced(2, 1, 30, 40);  // [1,2,30,40,4,5]
console.log(arr);  // [1,2,3,4,5](原数组不变)

Array.prototype.with()

返回替换指定索引元素后的新数组:

let arr = [1, 2, 3, 4, 5];

// 旧方式
let copy = [...arr];
copy[2] = 30;

// 新方式
let updated = arr.with(2, 30);  // [1,2,30,4,5]
console.log(arr);  // [1,2,3,4,5](原数组不变)

// 支持负数索引
arr.with(-1, 50);  // [1,2,3,4,50]

四个方法对比

方法对应的旧方法是否修改原数组
toReversed()reverse()❌ 不修改
toSorted()sort()❌ 不修改
toSpliced()splice()❌ 不修改
with(index, value)arr[index] = value❌ 不修改

为什么需要不可变方法?

// React 状态管理需要不可变更新
const [items, setItems] = useState([1, 2, 3]);

// 旧写法
setItems([...items].reverse());

// 新写法
setItems(items.toReversed());

// Redux 中
case 'REVERSE':
  return { ...state, list: state.list.toReversed() };

2. Hashbang 语法

允许在 JS 文件的首行使用 #!(Shebang)声明解释器:

#!/usr/bin/env node
console.log('Hello from Node.js');
#!/usr/bin/env --silent deno
console.log('Hello from Deno');

使用方式

# 直接执行
chmod +x script.js
./script.js

规则

  • #! 必须在文件的第一行
  • 前面不能有任何空白或其他字符
  • Node.js 从 v16 开始支持

3. 从后查找数组元素

Array.prototype.findLast()

从数组末尾开始查找第一个满足条件的元素:

let arr = [1, 2, 3, 4, 5, 4, 3];

arr.find(x => x > 3);      // 4(第一个大于3的)
arr.findLast(x => x > 3);  // 4(最后一个大于3的)

// 找到最后一个偶数
arr.findLast(x => x % 2 === 0);  // 4

// 找到某个符合条件的对象
let users = [
  { id: 1, name: '张三', active: false },
  { id: 2, name: '李四', active: true },
  { id: 3, name: '王五', active: true }
];

users.findLast(u => u.active);  // { id: 3, name: '王五', active: true }

Array.prototype.findLastIndex()

从数组末尾开始查找第一个满足条件的元素索引:

let arr = [1, 2, 3, 4, 5, 4, 3];

arr.findIndex(x => x > 3);      // 3(第一个大于3的索引)
arr.findLastIndex(x => x > 3);  // 5(最后一个大于3的索引)

// 找不到返回 -1
arr.findLastIndex(x => x > 10);  // -1

// 实际应用:从后往前找某个对象
let logs = [
  { level: 'info', msg: '启动' },
  { level: 'error', msg: '连接失败' },
  { level: 'info', msg: '重试' },
  { level: 'error', msg: '再次失败' }
];

let lastErrorIndex = logs.findLastIndex(l => l.level === 'error');
// 3

与 reverse + find 的对比

// 旧写法:需要反转再查找,或用循环
arr.slice().reverse().find(x => x > 3);

// 新写法:直接从后查找
arr.findLast(x => x > 3);

// 新写法性能更好:不需要创建新数组

4. Symbol 作为 WeakMap 的键

ES14 允许使用 Symbol 作为 WeakMap 的键(之前只能用对象):

let weakMap = new WeakMap();

let sym = Symbol('description');
weakMap.set(sym, 'value associated with symbol');
weakMap.get(sym);  // 'value associated with symbol'

使用场景

// 使用 Symbol 关联私有数据
const privateData = new WeakMap();

function createClass() {
  const key = Symbol('private');
  
  return class {
    constructor(value) {
      privateData.set(key, value);
    }
    
    getValue() {
      return privateData.get(key);
    }
  };
}

5. TypedArray 也支持部分 Change Array by Copy 方法

除了普通数组,toReversed()toSorted()with() 也扩展到了 TypedArray。

注意: TypedArray 不支持 toSpliced(),因为 TypedArray 的长度是固定的,无法增删元素。

let typed = new Int8Array([3, 1, 4, 1, 5]);

typed.toReversed();   // Int8Array [5, 1, 4, 1, 3]
typed.toSorted();     // Int8Array [1, 1, 3, 4, 5]
typed.with(2, 40);    // Int8Array [3, 1, 40, 1, 5]

总结

特性说明重要性
toReversed()不可变反转⭐⭐⭐⭐
toSorted()不可变排序⭐⭐⭐⭐
toSpliced()不可变删除/插入⭐⭐⭐⭐
with()不可变索引替换⭐⭐⭐⭐
Hashbang #!声明脚本解释器⭐⭐
findLast()从后查找元素⭐⭐⭐
findLastIndex()从后查找索引⭐⭐⭐
Symbol 作 WeakMap 键扩展 WeakMap 键类型⭐⭐