发布时间:2017年6月 ES8 新增了异步编程的关键特性,同时完善了字符串、对象等基础能力。
1. async/await
ES8 最重要的特性,让异步代码看起来像同步代码。
基本语法
js
async function fetchData() {
let res = await fetch('/api/data');
let data = await res.json();
return data;
}
工作原理
async函数总是返回一个 Promiseawait只能在async函数内使用await暂停函数执行,等待 Promise 结果
对比回调地狱和 Promise 链
js
// 回调地狱
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);
}
错误处理
js
// 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));
并行执行多个异步操作
js
// 串行执行(慢)
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];
}
立即执行
js
(async function() {
let data = await fetchData();
console.log(data);
})();
注意事项
await只能等待 Promise,非 Promise 值会自动包装- 在循环中慎用
await(会导致串行执行) - 顶层 await 需要 ES2022(模块环境下)
2. Object.values()
返回对象自身所有可枚举属性值的数组:
js
let obj = { a: 1, b: 2, c: 3 };
Object.values(obj); // [1, 2, 3]
使用场景
js
// 获取所有属性值
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 一致
- 字符串对象也能用
js
Object.values('hello'); // ['h', 'e', 'l', 'l', 'o']
3. Object.entries()
返回对象自身可枚举属性的键值对数组:
js
let obj = { a: 1, b: 2, c: 3 };
Object.entries(obj); // [['a', 1], ['b', 2], ['c', 3]]
配合 for...of 遍历
js
for (let [key, value] of Object.entries(obj)) {
console.log(key, value);
}
将对象转为 Map
js
let map = new Map(Object.entries(obj));
使用场景
js
// 过滤对象属性
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()- 字符串对象也能用
js
Object.entries('ab'); // [['0', 'a'], ['1', 'b']]
4. String.prototype.padStart()
字符串头部补全:
js
'5'.padStart(2, '0'); // '05',补到2位
'5'.padStart(4, '0'); // '0005',补到4位
'abc'.padStart(5, 'xy'); // 'xyabc',从左补
'abc'.padStart(2); // 'abc',超过长度不截断
语法
js
str.padStart(targetLength[, padString])
实际应用
js
// 日期格式化
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()
字符串尾部补全:
js
'5'.padEnd(2, '0'); // '50'
'abc'.padEnd(5, '.'); // 'abc..'
'abc'.padEnd(5, 'xy'); // 'abcxy'
'abc'.padEnd(2); // 'abc',超过长度不截断
实际应用
js
// 对齐输出
console.log('姓名'.padEnd(10, ' ') + '分数');
console.log('张三'.padEnd(10, ' ') + '90');
console.log('李四'.padEnd(10, ' ') + '85');
// 输出:
// 姓名 分数
// 张三 90
// 李四 85
注意
- 如果补全字符串长度超过目标长度,会截断补全字符串
js
'abc'.padStart(6, '123456'); // '123abc',截断为'123'
'abc'.padEnd(6, '123456'); // 'abc123'
6. Object.getOwnPropertyDescriptors()
获取对象自身所有属性的描述符:
js
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(单数)
js
// 单数:获取单个属性描述符(ES5)
Object.getOwnPropertyDescriptor(obj, 'name');
// 复数:获取所有属性描述符(ES8)
Object.getOwnPropertyDescriptors(obj);
实际用途:深拷贝 + 正确拷贝 getter/setter
js
// 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 新增的是:函数定义的参数列表 和函数调用的参数列表也允许写尾随逗号。对象字面量和数组字面量更早以前就已经支持尾随逗号了。
js
// 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 共享同一块内存:
js
let sab = new SharedArrayBuffer(1024); // 1KB 共享内存
let arr = new Int32Array(sab);
arr[0] = 42;
Atomics
提供原子操作,防止竞态条件:
js
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 |
共享内存和原子操作 | ⭐(特殊场景) |