一口氣看完,ES8,9,10,13,14,15中30多個(gè)最具變革性的JavaScript特性
ES8包含了許多有價(jià)值的特性,徹底改變了我們編寫(xiě)JavaScript的方式。
代碼變得更簡(jiǎn)潔、更易編寫(xiě),并升級(jí)了新功能。
我們來(lái)看看這些特性,看看你是否錯(cuò)過(guò)了哪些。
1.尾隨逗號(hào)
在ES8之前,尾隨逗號(hào)會(huì)導(dǎo)致語(yǔ)法錯(cuò)誤!
? 之前:
const colors = [
'red',
'blue',
'green',
'yellow', // ? 不允許
];
const person = {
name: 'Tari Ibaba',
site: 'codingbeautydev.com' // ? 不行
};
但這引發(fā)了一些問(wèn)題,重新排列列表會(huì)帶來(lái)麻煩:
圖片
我們還必須總是在最后一項(xiàng)添加逗號(hào)才能添加新項(xiàng) — 這會(huì)使git差異變得混亂:
圖片
所以ES8修復(fù)了所有這些:
? 現(xiàn)在:
const colors = [
'red',
'blue',
'green',
'yellow', // ? yes
];
const person = {
name: 'Tari Ibaba',
site: 'codingbeautydev.com', // ? yes
};
它們帶來(lái)的好處也使得像Prettier這樣的工具在格式化后默認(rèn)添加它們:
圖片
2.async/await
這就是async/await的起源!
不再需要煩人的then()嵌套:
? 之前:
wait().then(() => {
console.log('WHERE ARE YOU?! ??');
});
function wait() {
return new Promise((resolve) =>
setTimeout(resolve, 10 * 1000)
);
}
? 現(xiàn)在:
// ?? immediately invoked function expression (IIFE)
(async () => {
await wait();
console.log('WHERE ARE YOU?! ??');
})();
function wait() {
return new Promise((resolve) =>
setTimeout(resolve, 10 * 1000)
);
}
區(qū)別很明顯:
? 之前:
function getSuggestion() {
fetch('https://api.example/suggestion', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({}) // Pass the necessary payload here
})
.then((res) => {
return res.json();
})
.then((data) => {
const { suggestion } = data;
console.log(suggestion);
});
}
? 現(xiàn)在:
async function getSuggestion() {
const res = await fetch('https://api.example/suggestion');
const { suggestion } = await res.json();
console.log(suggestion);
}
10行 → 3行。
使用async/await,我們終于可以為異步代碼使用原生的 try-catch:
? ES8之前:
startWorkout();
function startWorkout() {
goToGym()
.then((result) => {
console.log(result);
})
.catch((err) => {
console.log(err);
});
}
function goToGym() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
reject(new Error("I'm tired today!??"));
}
resolve("Let's go!??♂?");
});
}
? 現(xiàn)在:
startWorkout();
// ? async/await
async function startWorkout() {
try {
await goToGym();
} catch (err) {
console.log(err);
}
}
function goToGym() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
reject(new Error("I'm tired today!??"));
}
resolve("Let's go!??♂?");
});
}
3.強(qiáng)大的Object靜態(tài)方法
Object.values()
一個(gè)出色的靜態(tài)方法,可以將對(duì)象的所有值提取到一個(gè)數(shù)組中:
const person = {
name: 'Tari Ibaba',
site: 'codingbeautydev.com',
color: '??blue',
};
const arr = Object.values(person);
// ['Tari Ibaba', 'codingbeautydev.com', '??blue']
console.log(arr);
非常適合數(shù)據(jù)可視化:
const fruits = [
{
name: 'Banana',
pic: '??',
color: 'yellow',
},
{
name: 'Apple',
pic: '??',
color: 'red',
},
];
const keys = Object.keys(fruits.at(0));
const header = keys.map((key) => `| ${key} |`).join('');
const rows = fruits
.map((fruit) =>
keys.map((key) => `| ${fruit[key]} |`).join('')
).join('\n');
console.log(header + '\n' + rows);
圖片
Object.entries()
const person = {
name: 'Tari Ibaba',
site: 'codingbeautydev.com',
color: '??blue',
};
const arr = Object.entries(person);
/*
[
['name', 'Tari Ibaba'],
['site', 'codingbeautydev.com'],
['color', '??blue']
]
*/
console.log(arr);
將對(duì)象中的每個(gè)鍵值對(duì)捆綁在一起,生成一個(gè)元組列表:
非常適合使用對(duì)象的鍵和值進(jìn)行數(shù)據(jù)轉(zhuǎn)換:
以ID為鍵的對(duì)象 → 對(duì)象列表:
? 之前:
const tasks = {
1: {
title: '???HIIT 30 minutes today',
complete: false,
},
2: {
name: 'Buy the backpack??',
complete: true,
},
};
const taskList = Object.keys(tasks).map((id) => ({
id,
...tasks[id],
}));
console.log(taskList);
? 現(xiàn)在:
// ? 更簡(jiǎn)潔
const taskList = Object.entries(tasks).map(
([id, task]) => ({
id,
...task,
})
);
console.log(taskList);
圖片
4.原生字符串填充
2016年3月22日,流行的NPM包left-pad被創(chuàng)建者作為一種抗議形式下架,導(dǎo)致數(shù)千個(gè)軟件項(xiàng)目崩潰。
這讓許多人擔(dān)心我們可能過(guò)度依賴外部模塊 — 即使是像字符串填充這樣簡(jiǎn)單的功能。
但幸運(yùn)的是,ES8為JavaScript帶來(lái)了原生的字符串填充功能,即padStart和padEnd字符串方法:
const name = 'tari';
console.log(name.padStart(9, ' ')); // ' tari'
console.log(name.padEnd(10, '??')); // 'tari????????'
我們不再需要依賴另一個(gè)第三方依賴。
5. Object.getOwnPropertyDescriptors()
名字聽(tīng)起來(lái)有點(diǎn)花哨,但很容易理解。
描述符是屬性的屬性 — 以下之一:
- value
- enumerable
- get
- set
- configurable
- enumerable
const person = {
name: 'Tari Ibaba',
color: '??color',
age: 999,
greet: () => console.log('Hey!'),
};
console.log(
Object.getOwnPropertyDescriptors(person)
);
圖片
最后的思考
總的來(lái)說(shuō),ES8對(duì)JavaScript來(lái)說(shuō)是一個(gè)重大飛躍,引入了幾個(gè)已成為現(xiàn)代開(kāi)發(fā)必不可少的特性。使你能夠編寫(xiě)更簡(jiǎn)潔、更富表現(xiàn)力和更清晰的代碼。
過(guò)去10年里,JavaScript取得了長(zhǎng)足進(jìn)步,每年都有全新的功能升級(jí)。
今天,我們來(lái)看看早期ES9中引入的5個(gè)最重要的特性,看看你是否錯(cuò)過(guò)了其中一些。
1. 異步生成器和迭代
異步生成器是ES9中一個(gè)強(qiáng)大的特性。
就像普通的生成器,但現(xiàn)在它可以在異步工作(如網(wǎng)絡(luò)請(qǐng)求)后彈出值:
function* asyncGenerator() {
yield new Promise((resolve) =>
setTimeout(() => resolve('done this ?'), 2000)
);
yield new Promise((resolve) =>
setTimeout(() => resolve('done that ?'), 3000)
);
}
當(dāng)我們調(diào)用.next()時(shí),我們會(huì)得到一個(gè)Promise:
const asyncGen = asyncGenerator();
asyncGen.next().value.then(console.log);
asyncGen.next().value.then(console.log);
這是一個(gè)強(qiáng)大的工具,可以在web應(yīng)用中以結(jié)構(gòu)化+可讀的方式流式傳輸數(shù)據(jù) — 看看這個(gè)為類似YouTube的視頻分享應(yīng)用緩沖和流式傳輸數(shù)據(jù)的函數(shù):
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);
}
}
現(xiàn)在要消費(fèi)這個(gè)生成器,我們將使用for await of — 異步迭代:
for await (const chunk of streamVideo({ id: 2341 })) {
// process video chunk
}
我想知道實(shí)際的YouTube JavaScript代碼是否使用了這樣的生成器?
2.對(duì)象的剩余/展開(kāi)運(yùn)算符
毫無(wú)疑問(wèn),你在某處遇到過(guò)現(xiàn)代的展開(kāi)語(yǔ)法。
這是一種快速且不可變地克隆數(shù)組的天才方法:
const colors = ['??', '??', '??'];
console.log([...colors, '??']);
// ['??', '??', '??', '??']
在ES6之前我們從未有過(guò)它,現(xiàn)在它無(wú)處不在。
Redux就是一個(gè)重要的例子:
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開(kāi)始,它也適用于對(duì)象:
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++' }
這使得它特別適合在默認(rèn)值的基礎(chǔ)上構(gòu)建,尤其是在制作公共實(shí)用程序時(shí)。
或者像我用Material UI定制默認(rèn)主題那樣:
圖片
使用展開(kāi)語(yǔ)法,你甚至可以去掉不想在副本中出現(xiàn)的對(duì)象屬性。
const colors = {
yellow: '??',
blue: '??',
red: '??',
};
const { yellow, ...withoutYellow } = colors;
console.log(withoutYellow);
// Output: { blue: '??', red: '??' }
這就是如何以不可變的方式從對(duì)象中移除屬性。
3. String.raw
當(dāng)我使用String.raw時(shí),我是在說(shuō):只給我我給你的東西。不要處理任何東西。不要?jiǎng)幽切┺D(zhuǎn)義字符:
不再需要轉(zhuǎn)義反斜杠,我們不用寫(xiě):
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
而是寫(xiě):
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
非常適合編寫(xiě)帶有大量這些反斜杠的正則表達(dá)式:
像這樣但更糟:
從這個(gè)?:
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']
到這個(gè)?:
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()但沒(méi)有String.cooked()。
4. 復(fù)雜的正則表達(dá)式特性
說(shuō)到正則表達(dá)式,ES9并沒(méi)有讓人失望。
它完全裝載了最先進(jìn)的正則表達(dá)式特性,用于高級(jí)字符串搜索和替換。
向后查找斷言
這是一個(gè)新特性,用于確保只有某個(gè)特定模式出現(xiàn)在你要搜索的內(nèi)容之前:
- 正向后查找:白名單 ?<=pattern
- 負(fù)向后查找:黑名單 ?<!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']
圖片
命名捕獲組
捕獲組一直是正則表達(dá)式中最寶貴的特性之一,用于以復(fù)雜的方式轉(zhuǎn)換字符串。
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
通常,這些組按照它們?cè)谡齽t表達(dá)式中的相對(duì)位置命名:1, 2, 3...
但這使得理解和更改那些愚蠢的長(zhǎng)正則表達(dá)式變得更加困難。
所以ES9通過(guò)?<name>來(lái)命名捕獲組解決了這個(gè)問(wèn)題:
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
圖片
你知道當(dāng)VS Code中出現(xiàn)錯(cuò)誤時(shí),你可以快速Alt + 點(diǎn)擊跳轉(zhuǎn)到錯(cuò)誤發(fā)生的確切位置嗎???
圖片
VS Code使用捕獲組使文件名可點(diǎn)擊,從而實(shí)現(xiàn)這種快速導(dǎo)航。
我想它大概是這樣的:
// 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 notallow="navigateWithButtonFilepointInnerText">filePoint</button>
5. Promise.finally
最后我們有了Promise.finally ??。
你知道finally總是會(huì)運(yùn)行一些代碼,無(wú)論是否有錯(cuò)誤嗎?
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就像那樣,但是用于異步任務(wù):
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()最大的優(yōu)點(diǎn)是當(dāng)你鏈接許多Promise時(shí):
它也能很好地與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帶來(lái)的。
最后的思考
ES9標(biāo)志著JavaScript的一個(gè)重大飛躍,引入了幾個(gè)對(duì)現(xiàn)代開(kāi)發(fā)至關(guān)重要的特性。使你能夠快速編寫(xiě)更清晰、更簡(jiǎn)潔、更富表現(xiàn)力的代碼。
JavaScript在過(guò)去10年里取得了長(zhǎng)足的進(jìn)步,每一年都有全新的功能升級(jí)。
還記得我們以前是這樣創(chuàng)建"類"的嗎?
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
是的,變化很大!
讓我們來(lái)看看ES10(2019年)中引入的7個(gè)最重要的特性,看看你是否錯(cuò)過(guò)了其中一些。
1. 即時(shí)模塊化:動(dòng)態(tài)import
ES10那年很棒,import現(xiàn)在可以像require()一樣作為函數(shù)使用。一個(gè)async函數(shù)。
將import保持在頂層不再是必須的;我們現(xiàn)在可以在編譯時(shí)輕松解析模塊的名稱。
為了高性能,可以選擇性地只在絕對(duì)需要時(shí)加載模塊...
if (user.is_admin) {
const admin = await import('./admin.js');
admin.setupDashboard();
}
基于用戶或變量輸入加載模塊...
const language = 'french';
const translations = await import(`./translations/${language}.js`);
它也非常適合使用不再支持require()的ES模塊:
2. 扁平化曲線
flat()和flatMap()提供了更清晰的方式來(lái)輕松扁平化多維數(shù)組。
消除了痛苦的數(shù)組循環(huán)扁平化代碼的需求:
圖片
flatMap()相當(dāng)于調(diào)用map(),然后flat(1):
圖片
3. 將數(shù)組轉(zhuǎn)換為對(duì)象
ES10還引入了Object.fromEntries()到JavaScript世界。
快速將鍵值對(duì)列表轉(zhuǎn)換為等效的鍵值對(duì)象:
const entries = [['name', 'John'], ['age', 30]];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'John', age: 30 }
4. 精確清理你的字符串
trimStart()和trimEnd()。
在此之前,每個(gè)人都在使用NPM的trim - 愉快地給項(xiàng)目增加3.35KB...
即使現(xiàn)在:
npm i trim
然后Array trim()出現(xiàn)了,接著是trimStart()和trimEnd()。
const str = ' Hello, World! ';
console.log(str.trimStart()); // 'Hello, World! '
console.log(str.trimEnd()); // ' Hello, World!'
5. 捕獲錯(cuò)誤而不帶包袱
通過(guò)新的可選catch綁定,當(dāng)你對(duì)錯(cuò)誤參數(shù)無(wú)所作為時(shí),現(xiàn)在可以安全地省略catch塊的錯(cuò)誤參數(shù):
圖片
6. 無(wú)驚喜排序
穩(wěn)定的數(shù)組排序。
以前,在對(duì)數(shù)組進(jìn)行排序時(shí),我們絕對(duì)無(wú)法保證相等元素的排列。
但在ES10之后的JS代碼中,我們100%確定react總是在vue之前,vue總是在angular之前。
圖片
圖片
7. 要么做大,要么回家:BigInt
BigInt的名稱揭示了它的目的:用于加載難以置信的巨大整數(shù)值:
圖片
圖片
因?yàn)槠胀ㄕ麛?shù)做不到:
圖片
最后的思考
ES10為JavaScript標(biāo)志著一個(gè)重要的飛躍,引入了幾個(gè)對(duì)現(xiàn)代開(kāi)發(fā)至關(guān)重要的特性。
使用它們來(lái)編寫(xiě)更清晰、更簡(jiǎn)潔、更具表現(xiàn)力和清晰度的代碼。
ES13包含了許多有價(jià)值的特性,徹底改變了我們編寫(xiě)JavaScript的方式。
從異步升級(jí)到數(shù)組語(yǔ)法糖等等,讓我們來(lái)看看這些特性,看看你是否錯(cuò)過(guò)了其中一些。
1. 頂級(jí)await
在ES13之前,我們永遠(yuǎn)不能在全局作用域中使用await。
? 之前:
// X 語(yǔ)法錯(cuò)誤:await 只在異步函數(shù)中有效
await setTimeoutAsync(3000);
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('codingbeautydev.com');
}, timeout);
});
}
我們總是必須將其放在async函數(shù)中或創(chuàng)建一個(gè)async IIFE(立即執(zhí)行函數(shù)表達(dá)式):
// 異步立即執(zhí)行函數(shù)
(async () => {
await setTimeoutAsync(3000);
})();
// 類似 C++
async function main() {
await setTimeoutAsync(3000);
}
? ES13之后:
// ? 等待超時(shí) - 沒(méi)有拋出錯(cuò)誤
await setTimeoutAsync(3000);
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('codingbeautydev.com');
}, timeout);
});
}
2. 類聲明升級(jí)
2.1 類字段聲明
在ES13之前,我們只能在構(gòu)造函數(shù)中聲明類字段: 與許多其他語(yǔ)言不同,我們不能在類的最外層作用域中聲明或定義它們。
? 之前:
? 現(xiàn)在有了ES13: 就像在TypeScript中一樣:
2.2 私有方法和字段
在ES13之前,創(chuàng)建私有方法是不可能的。 我們還必須使用丑陋的下劃線hack來(lái)表示私有性 — 但那只是一個(gè)指示。
? 之前:
class Person {
_firstName = 'Tari';
_lastName = 'Ibaba';
get name() {
return `${this._firstName} ${this._lastName}`;
}
}
const person = new Person();
console.log(person.name); // Tari Ibaba
// 我們?nèi)匀豢梢栽L問(wèn)私有成員!
console.log(person._firstName); // Tari
console.log(person._lastName); // Ibaba
// 它們也可以被修改!
person._firstName = 'Lionel';
person._lastName = 'Messi';
console.log(person.name); // Lionel Messi
? ES13之后:
我們可以通過(guò)在字段前加上井號(hào)(#)來(lái)為類添加私有字段和成員:
如果你試圖從類外部訪問(wèn)它,你會(huì)得到一個(gè)語(yǔ)法錯(cuò)誤:
class Person {
#firstName = 'Tari';
#lastName = 'Ibaba';
get name() {
return `${this.#firstName} ${this.#lastName}`;
}
}
const person = new Person();
console.log(person.name);
// 語(yǔ)法錯(cuò)誤:私有字段 '#firstName' 必須在封閉的類中聲明
console.log(person.#firstName);
console.log(person.#lastName);
我們可以從錯(cuò)誤消息中看到一些有趣的東西:
編譯器甚至不期望你從類外部嘗試訪問(wèn)私有字段 — 它假設(shè)你是在嘗試創(chuàng)建一個(gè)。
2.3 靜態(tài)類字段和靜態(tài)私有方法
靜態(tài)字段 — 類本身的屬性,而不是任何特定實(shí)例的屬性。
自ES13以來(lái),我們現(xiàn)在可以輕松地為任何類創(chuàng)建它們:
class Person {
static #count = 0;
static eyeCount = 2;
static getCount() {
// 使用 this 訪問(wèn)同級(jí)靜態(tài)成員
return this.#count;
}
// 實(shí)例成員
constructor() {
// 使用 this.constructor 訪問(wèn)靜態(tài)成員
this.constructor.#incrementCount();
}
static #incrementCount() {
this.#count++;
}
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount()); // 2
3. 數(shù)組升級(jí):新的at()方法
通常我們會(huì)使用方括號(hào)([])來(lái)訪問(wèn)數(shù)組的第N個(gè)元素。
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b
但從末尾訪問(wèn)第N個(gè)項(xiàng)目一直是一個(gè)痛點(diǎn) -- 我們必須使用arr.length - N進(jìn)行索引:
? ES13之前:
const arr = ['a', 'b', 'c', 'd'];
// 倒數(shù)第1個(gè)元素
console.log(arr[arr.length - 1]); // d
// 倒數(shù)第2個(gè)元素
console.log(arr[arr.length - 2]); // c
幸運(yùn)的是,ES13帶來(lái)了一個(gè)新的at()方法,解決了所有這些問(wèn)題:
const str = 'Coding Beauty';
console.log(str.at(-1)); // y 倒數(shù)第1個(gè)字符
console.log(str.at(-2)); // t 倒數(shù)第2個(gè)字符
4. 靜態(tài)類塊
隨著靜態(tài)字段的出現(xiàn),靜態(tài)塊也來(lái)了。 只在類創(chuàng)建時(shí)執(zhí)行一次代碼 — 就像C#和Java等OOP語(yǔ)言中的靜態(tài)構(gòu)造函數(shù)。 所以你可以在類中創(chuàng)建任意多個(gè)靜態(tài)塊 — 所有代碼都會(huì)按你定義的順序運(yùn)行:
class Vehicle {
static defaultColor = 'blue';
}
class Car extends Vehicle {
static colors = [];
// ?? pushes red before green
// ?? 先添加 red,然后添加 green
static {
this.colors.push(super.defaultColor, 'red');
}
static {
this.colors.push('green');
}
}
console.log(Car.colors); // ['blue', 'red', 'green']
5. 錯(cuò)誤報(bào)告升級(jí)
有時(shí)我們捕獲調(diào)用棧下方方法的錯(cuò)誤,只是為了將其重新拋出回調(diào)用棧上方。 但當(dāng)我們這樣做時(shí),我們會(huì)失去原始錯(cuò)誤中的關(guān)鍵信息:
try {
userAction();
} catch (err) {
// ? doesn't know fundamental cause of error
// ? 不知道錯(cuò)誤的根本原因
console.log(err);
}
function userAction() {
try {
apiCallThatCanThrow();
} catch (err) {
// ?? rethrow
// ?? 重新拋出錯(cuò)誤
throw new Error('New error message');
}
}
function apiCallThatCanThrow() {
console.log('fetching from codingbeautydev.com...');
throw new Error('throwing for no reason');
}
這就是為什么ES13引入了一個(gè)新的cause屬性來(lái)保留這個(gè)重要信息并使調(diào)試更容易:
try {
userAction();
} catch (err) {
// ? now knows what caused the error
// ? 現(xiàn)在知道了錯(cuò)誤的原因
console.log(err);
console.log(`Caused by: ${err.cause}`);
}
function userAction() {
try {
apiCallThatCanThrow();
} catch (err) {
// ? error cause
// ? 錯(cuò)誤原因
throw new Error('New error message', { cause: err });
}
}
function apiCallThatCanThrow() {
console.log('fetching from codingbeautydev.com...');
throw new Error('throwing for no reason');
}
最后的思考
總的來(lái)說(shuō),ES13對(duì)JavaScript來(lái)說(shuō)是一個(gè)重大飛躍,它帶來(lái)了幾個(gè)已成為現(xiàn)代開(kāi)發(fā)必不可少的特性。 使你能夠編寫(xiě)更清晰、更簡(jiǎn)潔、更具表現(xiàn)力的代碼。
JavaScript在過(guò)去10年里取得了長(zhǎng)足的進(jìn)步,每年都有全新的功能升級(jí)。 讓我們來(lái)看看ES14(2023年)中引入的5個(gè)最重要的特性,看看你是否錯(cuò)過(guò)了其中一些。
1. toSorted()
甜美的語(yǔ)法糖。
ES14的toSorted()方法使得排序數(shù)組并返回一個(gè)副本而不改變?cè)瓟?shù)組變得更加容易。
以前我們這樣做:
const numbers = [3, 1, 4, 1, 5];
const sorted = [...numbers].sort((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5]
console.log(numbers); // [3, 1, 4, 1, 5]
現(xiàn)在我們可以這樣做?:
const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.toSorted((a, b) => a - b);
console.log(sorted); // [1, 1, 3, 4, 5]
console.log(numbers); // [3, 1, 4, 1, 5]
toSorted()接受一個(gè)回調(diào)函數(shù)來(lái)控制排序行為 - 升序或降序,按字母順序或數(shù)字順序。就像sort()一樣。
2. toReversed()
另一個(gè)新的數(shù)組方法,用于促進(jìn)不可變性和函數(shù)式編程。
以前 — 使用reverse() ?:
const numbers = [1, 2, 3, 4, 5];
const reversed = numbers.reverse();
console.log(reversed); // [5, 4, 3, 2, 1]
console.log(numbers); // [5, 4, 3, 2, 1]
現(xiàn)在 — 使用toReversed() ?:
const numbers = [1, 2, 3, 4, 5];
const reversed = numbers.toReversed();
console.log(reversed); // [5, 4, 3, 2, 1]
console.log(numbers); // [1, 2, 3, 4, 5]
我發(fā)現(xiàn)這些不可變方法非常棒,可以不斷地鏈?zhǔn)秸{(diào)用方法,而不用擔(dān)心原始變量:
const result = numbers.toReversed().toSorted((a, b) => a - b);
3. toSpliced()
函數(shù)式編程愛(ài)好者無(wú)疑會(huì)對(duì)所有這些新的數(shù)組方法感到高興。 這是.splice()的不可變版本:
const items = [1, 2, 3, 4, 5];
const newItems = items.toSpliced(2, 1, 6, 7);
console.log(newItems); // [1, 2, 6, 7, 4, 5]
console.log(items); // [1, 2, 3, 4, 5]
4. 從末尾開(kāi)始查找數(shù)組
從第一項(xiàng)開(kāi)始搜索并不總是理想的:
圖片
你可以很容易地看到,對(duì)我們的巨大列表從末尾而不是開(kāi)始搜索會(huì)快得多。
圖片
有時(shí)你必須從末尾搜索才能讓你的程序工作。
比如我們想在一個(gè)數(shù)字列表中找到最后一個(gè)偶數(shù),find和findIndex會(huì)非常不準(zhǔn)確。 調(diào)用reverse()也不行,即使它會(huì)很慢:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const lastEven = numbers.reverse().find(n => n % 2 === 0);
console.log(lastEven); // 10(不正確)
所以在這種情況下,findLast()和findLastIndex()方法就派上用場(chǎng)了。
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const lastEven = numbers.findLast(n => n % 2 === 0);
console.log(lastEven); // 10(正確)
這段代碼更短、更易讀。最重要的是,它產(chǎn)生了正確的結(jié)果。
5. 數(shù)組的with()方法
with()是我們快速更改數(shù)組元素而不進(jìn)行任何突變的方法。
以前的常規(guī)方式:
const arr = [1, 2, 3, 4, 5];
const newArr = [...arr];
newArr[2] = 6;
console.log(newArr); // [1, 2, 6, 4, 5]
console.log(arr); // [1, 2, 3, 4, 5]
ES14現(xiàn)在讓我們這樣做:
const arr = [1, 2, 3, 4, 5];
const newArr = arr.with(2, 6);
console.log(newArr); // [1, 2, 6, 4, 5]
console.log(arr); // [1, 2, 3, 4, 5]
最后的思考
還有其他特性,但ES14主要是關(guān)于更容易的函數(shù)式編程和內(nèi)置的不可變性。 隨著React的興起,我們看到聲明式JavaScript爆炸式地流行起來(lái);很自然地,更多的這些特性會(huì)被烘焙到語(yǔ)言中,成為甜美的語(yǔ)法糖。
2024年:又是一個(gè)帶來(lái)全新JS特性升級(jí)的不可思議的年份,ES15推出。
從復(fù)雜的異步特性到語(yǔ)法糖數(shù)組和現(xiàn)代正則表達(dá)式,JavaScript編碼現(xiàn)在比以往任何時(shí)候都更簡(jiǎn)單、更快捷。
1.原生數(shù)組分組終于到來(lái)
Object.groupBy():
const fruits = [ { name: 'pineapple??', color: '??' }, { name: 'apple??', color: '??' }, { name: 'banana??', color: '??' }, { name: 'strawberry??', color: '??' },];const groupedByColor = Object.groupBy( fruits, (fruit, index) => fruit.color);// 原生 group by 示例console.log(groupedByColor);
圖片
字面意思就是讓恐龍級(jí)的 Lodash 庫(kù)失去了最后的存在理由 - 再也不需要了!
圖片
我原本期待一個(gè)新的實(shí)例方法,比如Array.prototype.groupBy,但不知什么原因他們把它做成了靜態(tài)方法。
然后我們還有Map.groupBy來(lái)用對(duì)象鍵進(jìn)行分組:
const array = [1, 2, 3, 4, 5];const odd = { odd: true };const even = { even: true };Map.groupBy(array, (num, index) => { return num % 2 === 0 ? even : odd;});// => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
不過(guò)幾乎沒(méi)人會(huì)這樣對(duì)數(shù)組分組,所以可能不會(huì)那么受歡迎。
2.從外部解決promise - 現(xiàn)代方式
使用Promise.withResolvers()。
從外部解決promises是很普遍的需求,在此之前我們不得不使用Deferred類來(lái)實(shí)現(xiàn):
class Deferred { constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); }}const deferred = new Deferred();deferred.resolve();
或者從NPM安裝 - 又多了一個(gè)依賴!
圖片
但現(xiàn)在有了ES15的Promise.withResolvers():
const { promise, resolve, reject } = Promise.withResolvers();
看看我如何用它來(lái)快速地將事件流promise化 - await一個(gè)observable:
// data-fetcher.jsconst { promise, resolve, reject } = Promise.withResolvers();function startListening() { eventStream.on('data', (data) => { resolve(data); });}async function getData() { return await promise;}// client.jsconst { startListening, getData } = require('./data-fetcher.js');startListening();// ? 監(jiān)聽(tīng)單個(gè)流事件const data = await getData();
3. Buffer性能升級(jí)
Buffers是用來(lái)存儲(chǔ)應(yīng)用程序生成的臨時(shí)數(shù)據(jù)的小型數(shù)據(jù)存儲(chǔ)。
它們使得在管道的各個(gè)階段之間傳輸和處理數(shù)據(jù)變得非常容易。
像這樣的管道:
- 文件處理: 輸入文件 → buffer → 處理 → 新buffer → 輸出文件
- 視頻流: 網(wǎng)絡(luò)響應(yīng) → buffer → 顯示視頻幀
- 餐廳隊(duì)列: 接待顧客 → 隊(duì)列/buffer → 服務(wù)顧客
const fs = require('fs');const { Transform } = require('stream');const inputFile = 'input.txt';const outputFile = 'output.txt';const inputStream = fs.createReadStream(inputFile, 'utf-8');const transformStream = new Transform({ transform(chunk) { // ? 從緩沖區(qū)轉(zhuǎn)換塊 },});const outputStream = fs.createWriteStream(outputFile);// ? 開(kāi)始管道inputStream.pipe(transformStream).pipe(outputStream);
使用 buffers,每個(gè)階段可以以不同的速度獨(dú)立處理數(shù)據(jù)。
但是當(dāng)通過(guò)管道移動(dòng)的數(shù)據(jù)超過(guò)buffer容量時(shí)會(huì)發(fā)生什么?
以前我們必須將當(dāng)前所有數(shù)據(jù)的buffer復(fù)制到一個(gè)更大的buffer中。
這對(duì)性能來(lái)說(shuō)很糟糕,尤其是當(dāng)管道中將有大量數(shù)據(jù)時(shí)。
ES15為我們提供了解決這個(gè)問(wèn)題的方案:可調(diào)整大小的數(shù)組buffers。
const resizableBuffer = new ArrayBuffer(1024, { maxByteLength: 1024 ** 2,});// ? 調(diào)整大小到 2048 字節(jié)resizableBuffer.resize(1024 * 2);
4.異步升級(jí)
Atomics.waitAsync(): ES2024中另一個(gè)強(qiáng)大的異步編碼特性:
它是當(dāng)2個(gè)代理共享一個(gè)buffer時(shí)...
代理1"睡眠"并等待代理2完成任務(wù)。
當(dāng)代理2完成時(shí),它使用共享buffer作為通道進(jìn)行通知。
const sharedBuffer = new SharedArrayBuffer(4096);const bufferLocation = new Int32Array(sharedBuffer);// 初始化緩沖區(qū)位置的初始值bufferLocation[37] = 0x1330;async function doStuff() { // ? agent 1:在共享緩沖區(qū)位置等待直到通知 Atomics.waitAsync(bufferLocation, 37, 0x1330).then( (r) => { /* 處理到達(dá) */ } );}function asyncTask() { // ? agent 2:在共享緩沖區(qū)位置通知 const bufferLocation = new Int32Array(sharedBuffer); Atomics.notify(bufferLocation, 37);}
如果你認(rèn)為這類似于普通的async/await,你絕對(duì)是對(duì)的。
但最大的區(qū)別是:這2個(gè)代理可以存在于完全不同的代碼上下文中 - 它們只需要訪問(wèn)相同的buffer。
而且:多個(gè)代理可以在不同時(shí)間訪問(wèn)或等待共享buffer - 其中任何一個(gè)都可以通知"喚醒"所有其他代理。
這就像P2P網(wǎng)絡(luò);而async/await更像是客戶端-服務(wù)器請(qǐng)求-響應(yīng)模式。
const sharedBuffer = new SharedArrayBuffer(4096);const bufferLocation = new Int32Array(sharedBuffer);bufferLocation[37] = 0x1330;// ? 從 postMessage() 接收到的共享緩沖區(qū)const code = `var ia = null;onmessage = function (ev) { if (!ia) { postMessage("Aux worker is running"); ia = new Int32Array(ev.data); } postMessage("Aux worker is sleeping for a little bit"); setTimeout(function () { postMessage("Aux worker is waking"); Atomics.notify(ia, 37); }, 1000);};`;async function doStuff() { // ? agent 1:存在于 Worker 上下文中 const worker = new Worker( 'data:application/javascript,' + encodeURIComponent(code) ); worker.onmessage = (event) => { // 記錄事件 }; worker.postMessage(sharedBuffer); Atomics.waitAsync(bufferLocation, 37, 0x1330).then( (r) => { /* 處理到達(dá) */ } );}function asyncTask() { // ? agent 2:在共享緩沖區(qū)位置通知 const bufferLocation = new Int32Array(sharedBuffer); Atomics.notify(bufferLocation, 37);}
5.正則表達(dá)式v標(biāo)志和集合操作
這是一個(gè)全新的特性,使正則表達(dá)式更加清晰和直觀。
使用表達(dá)式模式查找和操作復(fù)雜字符串 - 在集合操作的幫助下:
// A 和 B 是字符類,如 [a-z]// 差異:匹配 A 但不匹配 B[A--B]// 交集:同時(shí)匹配 A 和 B[A&&B]// 嵌套字符類[A--[0-9]]
匹配不斷增加的Unicode字符集,如:
- 表情符號(hào): ??, ??, ??, ??, 等
- 重音字母: é, à, ?, ?, 等
- 符號(hào)和非拉丁字符: ?, ?, €, £, μ, ¥, 等
所以這里我們使用Unicode正則表達(dá)式和v標(biāo)志來(lái)匹配所有希臘字母:
const regex = /[\p{Script_Extensinotallow=Greek}&&\p{Letter}]/v;
最后的想法
總的來(lái)說(shuō),ES15對(duì)JavaScript來(lái)說(shuō)是一個(gè)重大飛躍,包含了幾個(gè)對(duì)現(xiàn)代開(kāi)發(fā)至關(guān)重要的特性。幫助你以更簡(jiǎn)潔、更富表現(xiàn)力、更清晰的方式編寫(xiě)更干凈的代碼。