ES9中五個最具變革性的JavaScript特性
過去10年里,JavaScript取得了長足進步,每年都有全新的功能升級。
今天,我們來看看早期ES9中引入的5個最重要的特性,看看你是否錯過了其中一些。
1. 異步生成器和迭代
異步生成器是ES9中一個強大的特性。
就像普通的生成器,但現在它可以在異步工作(如網絡請求)后彈出值:
function* asyncGenerator() {
yield new Promise((resolve) =>
setTimeout(() => resolve('done this ?'), 2000)
);
yield new Promise((resolve) =>
setTimeout(() => resolve('done that ?'), 3000)
);
}
當我們調用.next()時,我們會得到一個Promise:
const asyncGen = asyncGenerator();
asyncGen.next().value.then(console.log);
asyncGen.next().value.then(console.log);
這是一個強大的工具,可以在web應用中以結構化+可讀的方式流式傳輸數據 — 看看這個為類似YouTube的視頻分享應用緩沖和流式傳輸數據的函數:
async function* streamVideo({ id }) {
let endOfVideo = false;
const downloadChunk = async (sizeInBytes) => {
const response = await fetch(
`api.example.com/videos/${id}`
);
const { chunk, done } = await response.json();
if (done) endOfVideo = true;
return chunk;
};
while (!endOfVideo) {
const bufferSize = 500 * 1024 * 1024;
yield await downloadChunk(bufferSize);
}
}
現在要消費這個生成器,我們將使用for await of — 異步迭代:
for await (const chunk of streamVideo({ id: 2341 })) {
// process video chunk
}
我想知道實際的YouTube JavaScript代碼是否使用了這樣的生成器?
2.對象的剩余/展開運算符
毫無疑問,你在某處遇到過現代的展開語法。
這是一種快速且不可變地克隆數組的天才方法:
const colors = ['??', '??', '??'];
console.log([...colors, '??']);
// ['??', '??', '??', '??']
在ES6之前我們從未有過它,現在它無處不在。
Redux就是一個重要的例子:
export default function userState(state = initialUserState, action) {
console.log(arr);
switch (action.type) {
case ADD_ITEM:
return {
...state,
arr: [...state.arr, action.newItem]
};
default:
return state;
}
}
從ES9開始,它也適用于對象:
const info = {
name: 'Coding Beauty',
site: 'codingbeautydev.com',
};
console.log({ ...info, theme: '??' });
/* Output:
{
name: 'Coding Beauty',
site: 'codingbeautydev.com',
theme: '??'
}
*/
覆蓋屬性:
const langs = {
j: 'java',
c: 'c++',
};
console.log({ ...langs, j: 'javascript' });
// Output: { j: 'javascript', c: 'c++' }
這使得它特別適合在默認值的基礎上構建,尤其是在制作公共實用程序時。
或者像我用Material UI定制默認主題那樣:
使用展開語法,你甚至可以去掉不想在副本中出現的對象屬性。
const colors = {
yellow: '??',
blue: '??',
red: '??',
};
const { yellow, ...withoutYellow } = colors;
console.log(withoutYellow);
// Output: { blue: '??', red: '??' }
這就是如何以不可變的方式從對象中移除屬性。
3. String.raw
當我使用String.raw時,我是在說:只給我我給你的東西。不要處理任何東西。不要動那些轉義字符:
不再需要轉義反斜杠,我們不用寫:
const filePath = 'C:\\Code\\JavaScript\\tests\\index.js';
console.log(`The file path is ${filePath}`);
// Output: The file path is C:\Code\JavaScript\tests\index.js
而是寫:
const filePath = String.raw`C:\Code\JavaScript\tests\index.js`;
console.log(`The file path is ${filePath}`);
// Output: The file path is C:\Code\JavaScript\tests\index.js
非常適合編寫帶有大量這些反斜杠的正則表達式:
像這樣但更糟:
從這個?:
const patternString = 'The (\\w+) is (\\d+)';
const pattern = new RegExp(patternString);
const message = 'The number is 100';
console.log(pattern.exec(message));
// ['The number is 100', 'number', '100']
到這個?:
const patternString = String.raw`The (\w+) is (\d+)`;
const pattern = new RegExp(patternString);
const message = 'The number is 100';
console.log(pattern.exec(message));
// ['The number is 100', 'number', '100']
所以"raw"意味著未處理的。
這就是為什么我們有String.raw()但沒有String.cooked()。
4. 復雜的正則表達式特性
說到正則表達式,ES9并沒有讓人失望。
它完全裝載了最先進的正則表達式特性,用于高級字符串搜索和替換。
向后查找斷言
這是一個新特性,用于確保只有某個特定模式出現在你要搜索的內容之前:
- 正向后查找:白名單 ?<=pattern
- 負向后查找:黑名單 ?<!pattern
const str = "It's just $5, and I have €20 and £50";
// Only match number sequence if $ comes first
const regexPos = /(?<=\$)\d+/g;
console.log(str.match(regexPos)); // ['5']
const regexNeg = /(?<!\$)\d+/g;
console.log(str.match(regexNeg)); // ['20', '50']
命名捕獲組
捕獲組一直是正則表達式中最寶貴的特性之一,用于以復雜的方式轉換字符串。
const str = 'The cat sat on a map';
// $1 -> [a-z]
// $2 -> a
// $3 -> t
// () indicates group
str.replace(/([a-z])(a)(t)/g, '$1*$3');
// -> The c*t s*t on a map
通常,這些組按照它們在正則表達式中的相對位置命名:1, 2, 3...
但這使得理解和更改那些愚蠢的長正則表達式變得更加困難。
所以ES9通過?<name>來命名捕獲組解決了這個問題:
const str = 'The cat sat on a map';
// left & right
console.log(str.replace(/(?<left>[a-z])(a)(?<right>t)/g, '$<left>*$<right>'));
// -> The c*t s*t on a map
你知道當VS Code中出現錯誤時,你可以快速Alt + 點擊跳轉到錯誤發生的確切位置嗎???
VS Code使用捕獲組使文件名可點擊,從而實現這種快速導航。
我想它大概是這樣的:
// The stupidly long regex
const regex = /(?<path>[a-z]:[a-z].(?:?:\\/|(?:\\/?)))[\w \-]+):(?<line>\d+):(?<char>\d+)/gi;
// ? String.raw!
const filePoint = String.raw`C:\coding-beauty\coding-beauty-javascript\index.js:3:5`;
const extractor = /(?<path>[a-z]:[a-z].(?:?:\\/|(?:\\/?)))[\w \-]+):(?<line>\d+):(?<char>\d+)/i;
const [path, lineStr, charStr] = filePoint
.match(regex)[0]
.match(extractor)
.slice(1, 4);
const line = Number(lineStr);
const char = Number(charStr);
console.log({ path, line, char });
// Replace all filePoint with <button> tag
// <button onclick="navigateWithButtonFilepointInnerText">filePoint</button>
5. Promise.finally
最后我們有了Promise.finally ??。
你知道finally總是會運行一些代碼,無論是否有錯誤嗎?
function startBodyBuilding() {
if (Math.random() > 0.5) {
throw new Error("I'm tired??");
}
console.log('Off to the gym ???♂???');
}
try {
startBodyBuilding();
} catch {
console.log('Stopped excuse??');
} finally {
console.log("I'm going!??♂?");
}
所以Promise.finally就像那樣,但是用于異步任務:
async function startBodyBuilding() {
await think();
if (Math.random() > 0.5) {
throw new Error("I'm tired??");
}
console.log('Off to the gym ???♂???');
}
startBodyBuilding()
.then(() => {
console.log('Started ?');
})
.catch(() => {
console.log('No excuses');
})
.finally(() => {
console.log("I'm going!??♂?");
});
Promise.finally()最大的優點是當你鏈接許多Promise時:
它也能很好地與Promise鏈一起工作:
getFruitApiUrl().then((url) => {
return fetch(url)
.then((res) => res.json())
.then((data) => {
fruits.push(data);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log(fruits);
});
});
這是由ES9帶來的。
最后的思考
ES9標志著JavaScript的一個重大飛躍,引入了幾個對現代開發至關重要的特性。使你能夠快速編寫更清晰、更簡潔、更富表現力的代碼。