ES8(ES2017)新特性

22 阅读5分钟

发布时间:2017年6月 ES8 新增了异步编程的关键特性,同时完善了字符串、对象等基础能力。


1. async/await

ES8 最重要的特性,让异步代码看起来像同步代码。

基本语法

async function fetchData() {
  let res = await fetch('/api/data');
  let data = await res.json();
  return data;
}

工作原理

  • async 函数总是返回一个 Promise
  • await 只能在 async 函数内使用
  • await 暂停函数执行,等待 Promise 结果

对比回调地狱和 Promise 链

// 回调地狱
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      console.log(c);
    });
  });
});

// Promise 链
getData()
  .then(a => getMoreData(a))
  .then(b => getEvenMoreData(b))
  .then(c => console.log(c))
  .catch(err => console.error(err));

// async/await,最清晰
async function getAll() {
  let a = await getData();
  let b = await getMoreData(a);
  let c = await getEvenMoreData(b);
  console.log(c);
}

错误处理

// try...catch 方式
async function fetchData() {
  try {
    let res = await fetch('/api/data');
    let data = await res.json();
    return data;
  } catch (err) {
    console.error('请求失败:', err);
  }
}

// 也可以用 .catch()
fetchData().catch(err => console.error(err));

并行执行多个异步操作

// 串行执行(慢)
async function serial() {
  let a = await fetch('/api/a');
  let b = await fetch('/api/b');
  return [a, b];
}

// 并行执行(快)
async function parallel() {
  let [a, b] = await Promise.all([
    fetch('/api/a'),
    fetch('/api/b')
  ]);
  return [a, b];
}

立即执行

(async function() {
  let data = await fetchData();
  console.log(data);
})();

注意事项

  • await 只能等待 Promise,非 Promise 值会自动包装
  • 在循环中慎用 await(会导致串行执行)
  • 顶层 await 需要 ES2022(模块环境下)

2. Object.values()

返回对象自身所有可枚举属性值的数组:

let obj = { a: 1, b: 2, c: 3 };
Object.values(obj);  // [1, 2, 3]

使用场景

// 获取所有属性值
let scores = { math: 90, english: 85, science: 92 };
let values = Object.values(scores);  // [90, 85, 92]

// 计算平均值
let avg = values.reduce((a, b) => a + b) / values.length;  // 89

// 检查是否有某个值
Object.values(obj).includes('target');

注意

  • 只返回自身的可枚举属性,不包括继承的
  • 属性顺序与 for...in 一致
  • 字符串对象也能用
Object.values('hello');  // ['h', 'e', 'l', 'l', 'o']

3. Object.entries()

返回对象自身可枚举属性的键值对数组:

let obj = { a: 1, b: 2, c: 3 };
Object.entries(obj);  // [['a', 1], ['b', 2], ['c', 3]]

配合 for...of 遍历

for (let [key, value] of Object.entries(obj)) {
  console.log(key, value);
}

将对象转为 Map

let map = new Map(Object.entries(obj));

使用场景

// 过滤对象属性
let filtered = Object.fromEntries(
  Object.entries(obj).filter(([k, v]) => v > 1)
);

// 映射对象值
let mapped = Object.fromEntries(
  Object.entries(obj).map(([k, v]) => [k, v * 2])
);

注意

  • Object.fromEntries() 是 ES10 才有的,ES8 只有 Object.entries()
  • 字符串对象也能用
Object.entries('ab');  // [['0', 'a'], ['1', 'b']]

4. String.prototype.padStart()

字符串头部补全:

'5'.padStart(2, '0');      // '05',补到2位
'5'.padStart(4, '0');      // '0005',补到4位
'abc'.padStart(5, 'xy');   // 'xyabc',从左补
'abc'.padStart(2);         // 'abc',超过长度不截断

语法

str.padStart(targetLength[, padString])

实际应用

// 日期格式化
let month = '5'.padStart(2, '0');   // '05'
let day = '9'.padStart(2, '0');     // '09'

// 序号格式化
'1'.padStart(3, '0');   // '001'
'42'.padStart(3, '0');  // '042'

// 卡号隐藏
'1234567890'.padStart(14, '*');  // '****1234567890'

5. String.prototype.padEnd()

字符串尾部补全:

'5'.padEnd(2, '0');      // '50'
'abc'.padEnd(5, '.');    // 'abc..'
'abc'.padEnd(5, 'xy');   // 'abcxy'
'abc'.padEnd(2);         // 'abc',超过长度不截断

实际应用

// 对齐输出
console.log('姓名'.padEnd(10, ' ') + '分数');
console.log('张三'.padEnd(10, ' ') + '90');
console.log('李四'.padEnd(10, ' ') + '85');

// 输出:
// 姓名       分数
// 张三       90
// 李四       85

注意

  • 如果补全字符串长度超过目标长度,会截断补全字符串
'abc'.padStart(6, '123456');  // '123abc',截断为'123'
'abc'.padEnd(6, '123456');    // 'abc123'

6. Object.getOwnPropertyDescriptors()

获取对象自身所有属性的描述符:

let obj = {
  name: '张三',
  get age() { return 18; }
};

Object.getOwnPropertyDescriptors(obj);
// {
//   name: { value: '张三', writable: true, enumerable: true, configurable: true },
//   age:  { get: [Function], set: undefined, enumerable: true, configurable: true }
// }

对比 getOwnPropertyDescriptor(单数)

// 单数:获取单个属性描述符(ES5)
Object.getOwnPropertyDescriptor(obj, 'name');

// 复数:获取所有属性描述符(ES8)
Object.getOwnPropertyDescriptors(obj);

实际用途:深拷贝 + 正确拷贝 getter/setter

// Object.assign 会丢失 getter/setter
let source = {
  get foo() { return 1; }
};
let copy = Object.assign({}, source);
// copy.foo = 1,变成普通值,不是 getter 了

// 正确的拷贝方式
let properCopy = Object.defineProperties(
  {},
  Object.getOwnPropertyDescriptors(source)
);
// properCopy.foo 是 getter,能正常工作

7. 函数参数末尾允许逗号

ES8 新增的是:函数定义的参数列表函数调用的参数列表也允许写尾随逗号。对象字面量和数组字面量更早以前就已经支持尾随逗号了。

// ES8 之前,对象和数组里就已经能写尾随逗号
let obj = {
  a: 1,
  b: 2,
};

let arr = [
  1,
  2,
];

// ES8 进一步允许函数参数列表写尾随逗号
function foo(
  a,
  b,
  c,
) {}

// 函数调用时也允许
foo(
  1,
  2,
  3,
);

好处

  • 添加新参数或重排参数时,git diff 更干净
  • 修改最后一项时不需要额外补逗号
  • 末尾逗号在语义上会被忽略,不影响执行

8. SharedArrayBuffer 和 Atomics

用于多线程编程。

SharedArrayBuffer

允许多个 Web Worker 共享同一块内存:

let sab = new SharedArrayBuffer(1024);  // 1KB 共享内存
let arr = new Int32Array(sab);
arr[0] = 42;

Atomics

提供原子操作,防止竞态条件:

let sab = new SharedArrayBuffer(4);
let arr = new Int32Array(sab);

// 在 Worker 中
Atomics.add(arr, 0, 5);    // 原子加5
Atomics.store(arr, 0, 10); // 原子写入
Atomics.load(arr, 0);      // 原子读取
Atomics.compareExchange(arr, 0, 10, 20); // 原子比较并交换
Atomics.wait(arr, 0, 10);  // 等待值变为10
Atomics.notify(arr, 0, 1); // 通知等待者

注意:由于安全原因(Spectre 漏洞),主流浏览器曾短暂禁用 SharedArrayBuffer,现在需要跨域隔离(Cross-Origin Isolation)才能使用。


总结

特性说明重要性
async/await异步编程语法糖⭐⭐⭐⭐⭐ 最重要的特性
Object.values()获取对象所有属性值⭐⭐⭐
Object.entries()获取对象键值对数组⭐⭐⭐
String.padStart()字符串头部补全⭐⭐⭐
String.padEnd()字符串尾部补全⭐⭐⭐
Object.getOwnPropertyDescriptors()获取所有属性描述符⭐⭐
函数参数末尾逗号函数参数末尾允许逗号⭐⭐
SharedArrayBuffer/Atomics共享内存和原子操作⭐(特殊场景)