記得嗎?兩週前我(或者說其實是我自己)還答應過你,從那之後我只會寫輕鬆、簡單的文章?嗯……我破戒了 😅 上週我發了一篇超長文章,主題是你能想像得到的最不性感的議題之一:遺留應用程式。沒想到即使如此,DEV 編輯團隊還是很喜歡,甚至把它選進了本週前 7 篇文章 ❤️
但今天總算輪到一個輕鬆一點的主題了。我覺得這個主題其實很值得多一點關注。
這些日子我們花很多時間在談 AI 代理人,我完全理解原因,因為這確實是個很迷人的領域。但我有時候會覺得,我們忘了自己每天使用的程式語言也在進化。
我大多數時間都在寫 JavaScript(好吧……當然是 TypeScript 😄)。沒錯,這幾年整個生態系已經成熟很多了。我們不再有沒完沒了的 React 對 Angular 大戰。也大致告別了那種每個人一個月前還愛死 Redux,六個月後又覺得它是個過度設計的怪物,然後把它從每個應用程式裡刪掉的年代 😅。
但生態系和 ECMAScript 標準都還在持續演進。
在 2015 年傳奇性的 ES6 發布之後,它徹底改變了前端開發,TC39 委員會改採迭代式的做法,開始每年推出新功能。
而好訊息是,如果你使用的是現代瀏覽器或執行環境,通常幾乎可以立刻用到這些功能。
那麼,在 ECMAScript 2026 裡我們得到了什麼?
對了,為了讓內容更有趣,本文中的所有程式碼範例,都是基於虛構的 LinkedIn 大師們充滿強烈觀點的內心獨白。若與現實中的任何人物有相似之處,當然純屬巧合 😇
本系列的其他文章:
MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/getOrInsert
這是那種會讓你忍不住想問:「等等……我們十年前為什麼沒有這個?」的功能之一。
想像我們正在收集 LinkedIn 大師們的觀點。到目前為止,我們得像這樣做:
const opinions = new Map();
function addOpinion(topic, author) {
if (!opinions.has(topic)) {
opinions.set(topic, []);
}
opinions.get(topic).push(author);
}
addOpinion("React is dead", "10x Engineer");
addOpinion("React is dead", "Principal AI Evangelist");
這樣是可以運作,但樣板程式碼不少。
有了 ES2026,事情就優雅多了:
const opinions = new Map();
opinions
.getOrInsert("React is dead", [])
.push("10x Engineer");
opinions
.getOrInsert("React is dead", [])
.push("Principal AI Evangelist");
console.log(opinions);
我們還有一個計算版本:
const opinions = new Map();
opinions
.getOrInsertComputed(
"沒有人再寫乾淨的程式碼了",
() => []
)
.push("DDD 教條派");
說真的,這樣乾淨又好讀多了。拿來做快取也超適合!程式碼更少,API 也更漂亮。
MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/concat
在談 concat() 之前,我們先快速回答一個問題:
Iterator 基本上就是一個游標,讓我們可以一次消耗一個元素的資料,而不需要把全部內容都存進記憶體。
例如:
const posts = [
"Angular is dead",
"JavaScript was a mistake"
];
const iterator = posts.values();
console.log(iterator.next());
// { value: "Angular is dead", done: false }
console.log(iterator.next());
// { value: "JavaScript was a mistake", done: false }
console.log(iterator.next());
// { value: undefined, done: true }
而且因為 iterator 是延遲求值的,我們甚至可以產生無限序列而不會把記憶體炸掉:
function* endlessHotTakes() {
while (true) {
yield "Everything should be rewritten in Rust.";
}
}
const iterator = endlessHotTakes();
console.log(iterator.next().value);
console.log(iterator.next().value);
console.log(iterator.next().value);
去年我們得到了 Iterator Helpers,新增了像 map()、filter() 之類的功能:
function* linkedinFeed() {
yield "Use Rust for everything";
yield "JavaScript was a mistake";
yield "Clean code is dead";
yield "Angular is dead";
}
const controversialTakes =
linkedinFeed()
.filter(post => post.includes("dead"))
.map(post => `🔥 ${post}`);
console.log([...controversialTakes]);
現在又多了一個很棒的補充:Iterator.concat()。假設前端大師和後端大師各自有自己的智慧串流:
function* frontendExperts() {
yield "React is dead";
yield "Nobody needs Redux";
}
function* backendExperts() {
yield "Microservices solve everything";
yield "Frontend developers don't understand architecture";
}
現在我們可以優雅地把它們合併:
const feed = Iterator.concat(
frontendExperts(),
backendExperts()
);
console.log([...feed]);
我認為,這些 iterator 相關功能得到的關注,遠遠少於它們應得的程度。
MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync
這基本上就是我們喜愛的 Array.from() 的非同步版本。
它主要是為非同步 iterator 設計的,不過我猜很多 JavaScript 開發者平常不會天天用到非同步 iterator。舉例來說,想像 LinkedIn 的留言一則一則湧入:
async function* angryComments() {
yield "這本來可以只是一個普通的 HTML 表單。";
yield "Angular is dead.";
yield "React is dead.";
yield "JavaScript was a mistake.";
}
const comments = await Array.fromAsync(
angryComments()
);
console.log(comments);
// 這本來可以只是一個普通的 HTML 表單。
// Angular is dead.
// 等等
但很多人沒有意識到的是:它也能搭配 promise 使用。
const opinions = [
Promise.resolve("沒有人應該使用類別。"),
Promise.resolve("Signals 改變了一切。"),
Promise.resolve("Microservices 毀了軟體。")
];
const takes = await Array.fromAsync(opinions);
console.log(takes);
而且你甚至可以立刻對值做映射:
const comments = await Array.fromAsync(
angryComments(),
opinion => opinion.toUpperCase()
);
console.log(comments);
相當方便。
MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sumPrecise
這個功能解決的是一個幾乎跟網際網路一樣古老的浮點數問題 😄 如大家所知,在 JavaScript 中:
console.log(0.1 + 0.2);
// 0.30000000000000004
今天我就不深入解釋為什麼會這樣:不然我們今晚都不用睡了 😄
重點是,ES2026 引入了 Math.sumPrecise(),它可以做更精確的加總,避免四捨五入誤差的累積。
假設某位 LinkedIn 大師列出所有據稱能提升生產力的事:
const productivityBoosts = [
0.1, // 冷水澡
0.2, // AI 代理人
0.3, // 寫日誌
0.4, // 凌晨 4 點起床
];
console.log(
Math.sumPrecise(productivityBoosts)
);
公平地說,大多數前端開發者大概永遠不需要這個。單純的加法在日常使用上其實完全沒問題。
但如果你在做金融、統計、模擬,或 AI/ML 相關工作,微小誤差會在成千上萬甚至百萬次運算中累積,這就會變得有趣得多。
MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/isError
這個功能大概比較偏向函式庫作者和進階開發者。或者不一定?😉 到目前為止,確認某個東西是不是實際的 error,通常像這樣:
try {
throw new Error(
"Angular is dead."
);
}
catch (e) {
console.log(
e instanceof Error
);
}
大多數時候,這完全沒問題。直到它出問題為止。
如果某個 error 來自另一個 realm,例如 iframe、Web Worker 或 VM context,instanceof Error 可能會突然背叛你:
const strangeThing =
window.frames[0].eval(
"new Error('JavaScript was a mistake.')"
);
console.log(
strangeThing instanceof Error
);
// false
這個問題多年來一直讓函式庫作者很頭痛,所以很多框架和函式庫都自己寫了檢查 error 的輔助函式。現在我們終於有內建解法了:
console.log(
Error.isError(strangeThing)
);
// true
簡單又漂亮。
MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/toBase64
快速提醒一下:Base64 就是一種把二進位資料轉成純文字的方式。為什麼要這麼做?因為許多通訊協定和格式,比起原始位元組,更喜歡處理文字。例如,我們可以把圖片當成 Base64 字串傳送。
現在有人可能會說:「等等,JavaScript 早就有 Base64 了啊!」沒錯,確實有。但它主要是跟字串一起用:
btoa("JavaScript was a mistake.");
但處理二進位資料時,事情很快就變得很醜:
const bytes = new Uint8Array(buffer);
const base64 = btoa(
String.fromCharCode(...bytes)
);
不算太糟,但絕對稱不上優雅。
現在我們只要這樣寫:
const screenshot =
await fetch(
"/proof/react-is-dead.png"
);
const bytes =
new Uint8Array(
await screenshot.arrayBuffer()
);
const base64 =
bytes.toBase64();
console.log(base64);
乾淨多了。也許更重要的是,六個月後會更容易看懂。
MDN:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
JSON.parse() 很棒。直到有人傳給你一個超大的數字。假設某位 LinkedIn 大師宣稱自己有 999 兆粉絲:
const json = `
{
"followers":
999999999999999999999999999999
}
`;
const data = JSON.parse(json);
console.log(data.followers);
糟了。JavaScript 的數字有上限,超大的整數可能會失去精度。
有了 ES2026,reviver 回呼可以存取原始來源文字,讓我們能安全地還原超大數值:
const data = JSON.parse(
json,
(key, value, context) => {
if (key === "followers") {
return BigInt(context.source);
}
return value;
}
);
console.log(data.followers);
不會再不小心把超大數字弄壞了。
我們大多數人不會每天都需要這個。但如果你在處理 ID、財務資料,或巨大的整數值,這可以幫你避免一些很惱人的 bug。
我原本真的很希望這些功能能進 ES2026,但很可惜我們還得再等一年。
呃,算是吧。現代瀏覽器和執行環境其實已經開始實作它們了,但它們還不是 ECMAScript 2026 規格的正式一部分。
這大概是我個人最期待的兩個功能。
TC39 提案:
https://github.com/tc39/proposal-temporal
如果你做前端開發夠久,八成至少曾經有過一次跟日期有關的創傷經驗。可能像這樣:
const date = new Date("2026-03-30");
console.log(date);
根據你的時區,恭喜!你剛進入了這個令人興奮的世界:「為什麼現在是昨天?」
或者你可能體驗過大家最愛的這個:
const today = new Date();
today.setMonth(today.getMonth() + 1);
然後你突然開始除錯,為什麼三月加一個月竟然變成了五月。
或者你可能很幸運地碰過時區問題。
這時你會發現,時間本身似乎只是幻覺,而日光節約時間就是專門為了摧毀程式設計師人生而發明的。
這也是為什麼很多人最後都改用像 Moment.js 這類函式庫。有趣的是,就連 Moment.js 維護者現在也建議使用其他方案。後來又出現了 date-fns、Luxon,以及其他各種試圖恢復理智的工具。
它們當然有幫助。直到有人說:「對了,澳洲的使用者回報怪 bug。」通常那就是好戲開始的地方。
Temporal 的目標就是透過引入正確的日期與時間型別來解決這一切。
不是:
const now = new Date();
而是像這樣:
const now = Temporal.Now.instant();
const birthday =
Temporal.PlainDate.from(
"1987-07-23"
);
const meeting =
Temporal.ZonedDateTime.from(
"2026-11-03T10:00:00[Europe/Warsaw]"
);
然後一切都突然變得更明確了。
順帶一提,我花了非常丟臉的時間,才搞清楚 Temporal 到底有沒有真的進入 ES2026。最後我找到 TC39 的儲存庫,上面清楚寫著:雖然這個提案已經到達 Stage 4,但預計發佈年份是 2027。
所以我們已經很接近了。非常接近。我等不及了 ❤️
using)TC39 提案:
https://github.com/tc39/proposal-explicit-resource-management
這是那種 C# 和 Python 開發者早就默默享受,而我們其他人卻假裝 try/finally 已經夠好的功能 😄
有時候你會建立一個之後需要清理的東西:檔案、資料庫連線、串流、WebSocket 等等。今天我們通常會這樣寫:
const webinar =
createAIAgentsWebinar();
try {
webinar.start();
}
finally {
webinar.close();
}
finally 區塊常常寫得像是在寫單元測試一樣不情不願。更糟的是,如果我們完全忘了它,可能會導致資源洩漏和神祕 bug。
有了 using,當離開作用域時就會自動清理:
using webinar =
createAIAgentsWebinar();
webinar.start();
一旦離開作用域,JavaScript 就會自動呼叫清理邏輯。是不是很酷?
這種功能可能不會每天影響一般前端開發者,但對 Node.js 開發者、函式庫作者,以及處理串流和檔案的人來說,會非常有用。
我得承認,這篇文章最後比我原本預期的還要費工得多 😅 本來應該只是短短輕鬆的一篇。結果我居然一路挖到了 TC39 提案、MDN 頁面、瀏覽器實作,還在努力確認 Temporal 到底有沒有進 ES2026。
不過我希望這些努力是值得的,也希望你喜歡這篇文章 ❤️
那你呢?哪個功能最讓你驚訝?而且你是不是也正熱切等待 Temporal 終於把我們從所有跟日期有關的痛苦中解放出來?
(或者至少這大概就是 LinkedIn 大師們下週會告訴我們的事 😇)
如果你喜歡這篇文章,歡迎到我的 LinkedIn 加我😊
原文出處:https://dev.to/sylwia-lask/7-new-javascript-features-and-2-im-still-waiting-for-2ck8