一口氣看完,ES8,9,10,13,14,15中30多個最具變革性的JavaScript特性
ES8包含了許多有價值的特性,徹底改變了我們編寫JavaScript的方式。
代碼變得更簡潔、更易編寫,并升級了新功能。
我們來看看這些特性,看看你是否錯過了哪些。
1.尾隨逗號
在ES8之前,尾隨逗號會導致語法錯誤!
? 之前:
const colors = [
'red',
'blue',
'green',
'yellow', // ? 不允許
];
const person = {
name: 'Tari Ibaba',
site: 'codingbeautydev.com' // ? 不行
};
但這引發(fā)了一些問題,重新排列列表會帶來麻煩:
圖片
我們還必須總是在最后一項添加逗號才能添加新項 — 這會使git差異變得混亂:
圖片
所以ES8修復了所有這些:
? 現(xiàn)在:
const colors = [
'red',
'blue',
'green',
'yellow', // ? yes
];
const person = {
name: 'Tari Ibaba',
site: 'codingbeautydev.com', // ? yes
};
它們帶來的好處也使得像Prettier這樣的工具在格式化后默認添加它們:
圖片
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.強大的Object靜態(tài)方法
Object.values()
一個出色的靜態(tài)方法,可以將對象的所有值提取到一個數(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);
將對象中的每個鍵值對捆綁在一起,生成一個元組列表:
非常適合使用對象的鍵和值進行數(shù)據(jù)轉換:
以ID為鍵的對象 → 對象列表:
? 之前:
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)在:
// ? 更簡潔
const taskList = Object.entries(tasks).map(
([id, task]) => ({
id,
...task,
})
);
console.log(taskList);
圖片
4.原生字符串填充
2016年3月22日,流行的NPM包left-pad被創(chuàng)建者作為一種抗議形式下架,導致數(shù)千個軟件項目崩潰。
這讓許多人擔心我們可能過度依賴外部模塊 — 即使是像字符串填充這樣簡單的功能。
但幸運的是,ES8為JavaScript帶來了原生的字符串填充功能,即padStart和padEnd字符串方法:
const name = 'tari';
console.log(name.padStart(9, ' ')); // ' tari'
console.log(name.padEnd(10, '??')); // 'tari????????'
我們不再需要依賴另一個第三方依賴。
5. Object.getOwnPropertyDescriptors()
名字聽起來有點花哨,但很容易理解。
描述符是屬性的屬性 — 以下之一:
- value
- enumerable
- get
- set
- configurable
- enumerable
const person = {
name: 'Tari Ibaba',
color: '??color',
age: 999,
greet: () => console.log('Hey!'),
};
console.log(
Object.getOwnPropertyDescriptors(person)
);
圖片
最后的思考
總的來說,ES8對JavaScript來說是一個重大飛躍,引入了幾個已成為現(xiàn)代開發(fā)必不可少的特性。使你能夠編寫更簡潔、更富表現(xiàn)力和更清晰的代碼。
過去10年里,JavaScript取得了長足進步,每年都有全新的功能升級。
今天,我們來看看早期ES9中引入的5個最重要的特性,看看你是否錯過了其中一些。
1. 異步生成器和迭代
異步生成器是ES9中一個強大的特性。
就像普通的生成器,但現(xiàn)在它可以在異步工作(如網(wǎng)絡請求)后彈出值:
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應用中以結構化+可讀的方式流式傳輸數(shù)據(jù) — 看看這個為類似YouTube的視頻分享應用緩沖和流式傳輸數(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)在要消費這個生成器,我們將使用for await of — 異步迭代:
for await (const chunk of streamVideo({ id: 2341 })) {
// process video chunk
}
我想知道實際的YouTube JavaScript代碼是否使用了這樣的生成器?
2.對象的剩余/展開運算符
毫無疑問,你在某處遇到過現(xiàn)代的展開語法。
這是一種快速且不可變地克隆數(shù)組的天才方法:
const colors = ['??', '??', '??'];
console.log([...colors, '??']);
// ['??', '??', '??', '??']
在ES6之前我們從未有過它,現(xiàn)在它無處不在。
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定制默認主題那樣:
圖片
使用展開語法,你甚至可以去掉不想在副本中出現(xiàn)的對象屬性。
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并沒有讓人失望。
它完全裝載了最先進的正則表達式特性,用于高級字符串搜索和替換。
向后查找斷言
這是一個新特性,用于確保只有某個特定模式出現(xiàn)在你要搜索的內(nèi)容之前:
- 正向后查找:白名單 ?<=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中出現(xiàn)錯誤時,你可以快速Alt + 點擊跳轉到錯誤發(fā)生的確切位置嗎???
圖片
VS Code使用捕獲組使文件名可點擊,從而實現(xiàn)這種快速導航。
我想它大概是這樣的:
// 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總是會運行一些代碼,無論是否有錯誤嗎?
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()最大的優(yōu)點是當你鏈接許多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的一個重大飛躍,引入了幾個對現(xiàn)代開發(fā)至關重要的特性。使你能夠快速編寫更清晰、更簡潔、更富表現(xiàn)力的代碼。
JavaScript在過去10年里取得了長足的進步,每一年都有全新的功能升級。
還記得我們以前是這樣創(chuàng)建"類"的嗎?
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
是的,變化很大!
讓我們來看看ES10(2019年)中引入的7個最重要的特性,看看你是否錯過了其中一些。
1. 即時模塊化:動態(tài)import
ES10那年很棒,import現(xiàn)在可以像require()一樣作為函數(shù)使用。一個async函數(shù)。
將import保持在頂層不再是必須的;我們現(xiàn)在可以在編譯時輕松解析模塊的名稱。
為了高性能,可以選擇性地只在絕對需要時加載模塊...
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()提供了更清晰的方式來輕松扁平化多維數(shù)組。
消除了痛苦的數(shù)組循環(huán)扁平化代碼的需求:
圖片
flatMap()相當于調用map(),然后flat(1):
圖片
3. 將數(shù)組轉換為對象
ES10還引入了Object.fromEntries()到JavaScript世界。
快速將鍵值對列表轉換為等效的鍵值對象:
const entries = [['name', 'John'], ['age', 30]];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: 'John', age: 30 }
4. 精確清理你的字符串
trimStart()和trimEnd()。
在此之前,每個人都在使用NPM的trim - 愉快地給項目增加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. 捕獲錯誤而不帶包袱
通過新的可選catch綁定,當你對錯誤參數(shù)無所作為時,現(xiàn)在可以安全地省略catch塊的錯誤參數(shù):
圖片
6. 無驚喜排序
穩(wěn)定的數(shù)組排序。
以前,在對數(shù)組進行排序時,我們絕對無法保證相等元素的排列。
但在ES10之后的JS代碼中,我們100%確定react總是在vue之前,vue總是在angular之前。
圖片
圖片
7. 要么做大,要么回家:BigInt
BigInt的名稱揭示了它的目的:用于加載難以置信的巨大整數(shù)值:
圖片
圖片
因為普通整數(shù)做不到:
圖片
最后的思考
ES10為JavaScript標志著一個重要的飛躍,引入了幾個對現(xiàn)代開發(fā)至關重要的特性。
使用它們來編寫更清晰、更簡潔、更具表現(xiàn)力和清晰度的代碼。
ES13包含了許多有價值的特性,徹底改變了我們編寫JavaScript的方式。
從異步升級到數(shù)組語法糖等等,讓我們來看看這些特性,看看你是否錯過了其中一些。
1. 頂級await
在ES13之前,我們永遠不能在全局作用域中使用await。
? 之前:
// X 語法錯誤:await 只在異步函數(shù)中有效
await setTimeoutAsync(3000);
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('codingbeautydev.com');
}, timeout);
});
}
我們總是必須將其放在async函數(shù)中或創(chuàng)建一個async IIFE(立即執(zhí)行函數(shù)表達式):
// 異步立即執(zhí)行函數(shù)
(async () => {
await setTimeoutAsync(3000);
})();
// 類似 C++
async function main() {
await setTimeoutAsync(3000);
}
? ES13之后:
// ? 等待超時 - 沒有拋出錯誤
await setTimeoutAsync(3000);
function setTimeoutAsync(timeout) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('codingbeautydev.com');
}, timeout);
});
}
2. 類聲明升級
2.1 類字段聲明
在ES13之前,我們只能在構造函數(shù)中聲明類字段: 與許多其他語言不同,我們不能在類的最外層作用域中聲明或定義它們。
? 之前:
? 現(xiàn)在有了ES13: 就像在TypeScript中一樣:
2.2 私有方法和字段
在ES13之前,創(chuàng)建私有方法是不可能的。 我們還必須使用丑陋的下劃線hack來表示私有性 — 但那只是一個指示。
? 之前:
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問私有成員!
console.log(person._firstName); // Tari
console.log(person._lastName); // Ibaba
// 它們也可以被修改!
person._firstName = 'Lionel';
person._lastName = 'Messi';
console.log(person.name); // Lionel Messi
? ES13之后:
我們可以通過在字段前加上井號(#)來為類添加私有字段和成員:
如果你試圖從類外部訪問它,你會得到一個語法錯誤:
class Person {
#firstName = 'Tari';
#lastName = 'Ibaba';
get name() {
return `${this.#firstName} ${this.#lastName}`;
}
}
const person = new Person();
console.log(person.name);
// 語法錯誤:私有字段 '#firstName' 必須在封閉的類中聲明
console.log(person.#firstName);
console.log(person.#lastName);
我們可以從錯誤消息中看到一些有趣的東西:
編譯器甚至不期望你從類外部嘗試訪問私有字段 — 它假設你是在嘗試創(chuàng)建一個。
2.3 靜態(tài)類字段和靜態(tài)私有方法
靜態(tài)字段 — 類本身的屬性,而不是任何特定實例的屬性。
自ES13以來,我們現(xiàn)在可以輕松地為任何類創(chuàng)建它們:
class Person {
static #count = 0;
static eyeCount = 2;
static getCount() {
// 使用 this 訪問同級靜態(tài)成員
return this.#count;
}
// 實例成員
constructor() {
// 使用 this.constructor 訪問靜態(tài)成員
this.constructor.#incrementCount();
}
static #incrementCount() {
this.#count++;
}
}
const person1 = new Person();
const person2 = new Person();
console.log(Person.getCount()); // 2
3. 數(shù)組升級:新的at()方法
通常我們會使用方括號([])來訪問數(shù)組的第N個元素。
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[1]); // b
但從末尾訪問第N個項目一直是一個痛點 -- 我們必須使用arr.length - N進行索引:
? ES13之前:
const arr = ['a', 'b', 'c', 'd'];
// 倒數(shù)第1個元素
console.log(arr[arr.length - 1]); // d
// 倒數(shù)第2個元素
console.log(arr[arr.length - 2]); // c
幸運的是,ES13帶來了一個新的at()方法,解決了所有這些問題:
const str = 'Coding Beauty';
console.log(str.at(-1)); // y 倒數(shù)第1個字符
console.log(str.at(-2)); // t 倒數(shù)第2個字符
4. 靜態(tài)類塊
隨著靜態(tài)字段的出現(xiàn),靜態(tài)塊也來了。 只在類創(chuàng)建時執(zhí)行一次代碼 — 就像C#和Java等OOP語言中的靜態(tài)構造函數(shù)。 所以你可以在類中創(chuàng)建任意多個靜態(tài)塊 — 所有代碼都會按你定義的順序運行:
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. 錯誤報告升級
有時我們捕獲調用棧下方方法的錯誤,只是為了將其重新拋出回調用棧上方。 但當我們這樣做時,我們會失去原始錯誤中的關鍵信息:
try {
userAction();
} catch (err) {
// ? doesn't know fundamental cause of error
// ? 不知道錯誤的根本原因
console.log(err);
}
function userAction() {
try {
apiCallThatCanThrow();
} catch (err) {
// ?? rethrow
// ?? 重新拋出錯誤
throw new Error('New error message');
}
}
function apiCallThatCanThrow() {
console.log('fetching from codingbeautydev.com...');
throw new Error('throwing for no reason');
}
這就是為什么ES13引入了一個新的cause屬性來保留這個重要信息并使調試更容易:
try {
userAction();
} catch (err) {
// ? now knows what caused the error
// ? 現(xiàn)在知道了錯誤的原因
console.log(err);
console.log(`Caused by: ${err.cause}`);
}
function userAction() {
try {
apiCallThatCanThrow();
} catch (err) {
// ? error cause
// ? 錯誤原因
throw new Error('New error message', { cause: err });
}
}
function apiCallThatCanThrow() {
console.log('fetching from codingbeautydev.com...');
throw new Error('throwing for no reason');
}
最后的思考
總的來說,ES13對JavaScript來說是一個重大飛躍,它帶來了幾個已成為現(xiàn)代開發(fā)必不可少的特性。 使你能夠編寫更清晰、更簡潔、更具表現(xiàn)力的代碼。
JavaScript在過去10年里取得了長足的進步,每年都有全新的功能升級。 讓我們來看看ES14(2023年)中引入的5個最重要的特性,看看你是否錯過了其中一些。
1. toSorted()
甜美的語法糖。
ES14的toSorted()方法使得排序數(shù)組并返回一個副本而不改變原數(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()接受一個回調函數(shù)來控制排序行為 - 升序或降序,按字母順序或數(shù)字順序。就像sort()一樣。
2. toReversed()
另一個新的數(shù)組方法,用于促進不可變性和函數(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)這些不可變方法非常棒,可以不斷地鏈式調用方法,而不用擔心原始變量:
const result = numbers.toReversed().toSorted((a, b) => a - b);
3. toSpliced()
函數(shù)式編程愛好者無疑會對所有這些新的數(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. 從末尾開始查找數(shù)組
從第一項開始搜索并不總是理想的:
圖片
你可以很容易地看到,對我們的巨大列表從末尾而不是開始搜索會快得多。
圖片
有時你必須從末尾搜索才能讓你的程序工作。
比如我們想在一個數(shù)字列表中找到最后一個偶數(shù),find和findIndex會非常不準確。 調用reverse()也不行,即使它會很慢:
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()方法就派上用場了。
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)生了正確的結果。
5. 數(shù)組的with()方法
with()是我們快速更改數(shù)組元素而不進行任何突變的方法。
以前的常規(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主要是關于更容易的函數(shù)式編程和內(nèi)置的不可變性。 隨著React的興起,我們看到聲明式JavaScript爆炸式地流行起來;很自然地,更多的這些特性會被烘焙到語言中,成為甜美的語法糖。
2024年:又是一個帶來全新JS特性升級的不可思議的年份,ES15推出。
從復雜的異步特性到語法糖數(shù)組和現(xiàn)代正則表達式,JavaScript編碼現(xiàn)在比以往任何時候都更簡單、更快捷。
1.原生數(shù)組分組終于到來
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);
圖片
字面意思就是讓恐龍級的 Lodash 庫失去了最后的存在理由 - 再也不需要了!
圖片
我原本期待一個新的實例方法,比如Array.prototype.groupBy,但不知什么原因他們把它做成了靜態(tài)方法。
然后我們還有Map.groupBy來用對象鍵進行分組:
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] }
不過幾乎沒人會這樣對數(shù)組分組,所以可能不會那么受歡迎。
2.從外部解決promise - 現(xiàn)代方式
使用Promise.withResolvers()。
從外部解決promises是很普遍的需求,在此之前我們不得不使用Deferred類來實現(xiàn):
class Deferred { constructor() { this.promise = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); }}const deferred = new Deferred();deferred.resolve();
或者從NPM安裝 - 又多了一個依賴!
圖片
但現(xiàn)在有了ES15的Promise.withResolvers():
const { promise, resolve, reject } = Promise.withResolvers();
看看我如何用它來快速地將事件流promise化 - await一個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)聽單個流事件const data = await getData();
3. Buffer性能升級
Buffers是用來存儲應用程序生成的臨時數(shù)據(jù)的小型數(shù)據(jù)存儲。
它們使得在管道的各個階段之間傳輸和處理數(shù)據(jù)變得非常容易。
像這樣的管道:
- 文件處理: 輸入文件 → buffer → 處理 → 新buffer → 輸出文件
- 視頻流: 網(wǎng)絡響應 → buffer → 顯示視頻幀
- 餐廳隊列: 接待顧客 → 隊列/buffer → 服務顧客
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ū)轉換塊 },});const outputStream = fs.createWriteStream(outputFile);// ? 開始管道inputStream.pipe(transformStream).pipe(outputStream);
使用 buffers,每個階段可以以不同的速度獨立處理數(shù)據(jù)。
但是當通過管道移動的數(shù)據(jù)超過buffer容量時會發(fā)生什么?
以前我們必須將當前所有數(shù)據(jù)的buffer復制到一個更大的buffer中。
這對性能來說很糟糕,尤其是當管道中將有大量數(shù)據(jù)時。
ES15為我們提供了解決這個問題的方案:可調整大小的數(shù)組buffers。
const resizableBuffer = new ArrayBuffer(1024, { maxByteLength: 1024 ** 2,});// ? 調整大小到 2048 字節(jié)resizableBuffer.resize(1024 * 2);
4.異步升級
Atomics.waitAsync(): ES2024中另一個強大的異步編碼特性:
它是當2個代理共享一個buffer時...
代理1"睡眠"并等待代理2完成任務。
當代理2完成時,它使用共享buffer作為通道進行通知。
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) => { /* 處理到達 */ } );}function asyncTask() { // ? agent 2:在共享緩沖區(qū)位置通知 const bufferLocation = new Int32Array(sharedBuffer); Atomics.notify(bufferLocation, 37);}
如果你認為這類似于普通的async/await,你絕對是對的。
但最大的區(qū)別是:這2個代理可以存在于完全不同的代碼上下文中 - 它們只需要訪問相同的buffer。
而且:多個代理可以在不同時間訪問或等待共享buffer - 其中任何一個都可以通知"喚醒"所有其他代理。
這就像P2P網(wǎng)絡;而async/await更像是客戶端-服務器請求-響應模式。
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) => { /* 處理到達 */ } );}function asyncTask() { // ? agent 2:在共享緩沖區(qū)位置通知 const bufferLocation = new Int32Array(sharedBuffer); Atomics.notify(bufferLocation, 37);}
5.正則表達式v標志和集合操作
這是一個全新的特性,使正則表達式更加清晰和直觀。
使用表達式模式查找和操作復雜字符串 - 在集合操作的幫助下:
// A 和 B 是字符類,如 [a-z]// 差異:匹配 A 但不匹配 B[A--B]// 交集:同時匹配 A 和 B[A&&B]// 嵌套字符類[A--[0-9]]
匹配不斷增加的Unicode字符集,如:
- 表情符號: ??, ??, ??, ??, 等
- 重音字母: é, à, ?, ?, 等
- 符號和非拉丁字符: ?, ?, €, £, μ, ¥, 等
所以這里我們使用Unicode正則表達式和v標志來匹配所有希臘字母:
const regex = /[\p{Script_Extensinotallow=Greek}&&\p{Letter}]/v;
最后的想法
總的來說,ES15對JavaScript來說是一個重大飛躍,包含了幾個對現(xiàn)代開發(fā)至關重要的特性。幫助你以更簡潔、更富表現(xiàn)力、更清晰的方式編寫更干凈的代碼。