2025 年的 JavaScript 與以前有什麼不一樣?

JavaScript 與他的前世今生

#軟體技術

2025 年的 JavaScript 與以前有什麼不一樣?

JavaScript 作為全世界使用最廣泛的腳本語言,至今已經超過了 25 個年頭。在這段時間裡,從 ES6(ES2015)誕生以來,JavaScript 的生態系統進入了快速演變的時期。

像夏格飛這樣的開發者,從早期 2008 年左右,引請期盼 JS 的改良,並為了 ES6 的到來而歡呼。然後默默到了 2025 年, JavaScript 已今非昔比,新的語法與功能極大提升了程式碼的可讀性與維護性,同時也為開發者減少許多不必要的麻煩。

那麼最新的 JavaScript 技術有什麼不同呢?讓夏格飛來導覽一次 JavaScript 的前世今生吧。

變數宣告方式:取代 var 的 let & const

傳統上,JavaScript 使用 var 作為變數的宣告關鍵字。然而,var 的作用域 (scope)往往造成混亂,因它是基於函數作用域而非區塊 (block) 作用域。而在 ES6 中,let 和 const 的引入讓區塊作用域成為可能,不僅更符合預期使用,也降低了變數重複聲明的錯誤風險。

範例:

for (let i = 0; i < 3; i++) {
    console.log(i); // 0, 1, 2
}

console.log(i); // ReferenceError!i 是無法存取的

相較之下,var 則會導致意想不到的結果:

for (var j = 0; j < 3; j++) {
    console.log(j); // 0, 1, 2
}

console.log(j); // 3,因為 var 是函數作用域

建議:選擇 const 用於宣告不可變的常數,let 用於需要重新賦值的變數,而完全避免使用 var。

類別 (Class) 替代傳統的函數 prototype 寫法

在過去,JavaScript 的物件導向是透過函式與 prototype 實現的,但其語法往往令人難以掌握。現代 JavaScript 提供了更易讀的 class 語法,讓開發者能以簡單直觀的方式定義類別與方法。

範例:舊版 prototype 寫法


function Person(name) {
    this.name = name;
}

Person.prototype.getName = function() {
    return this.name;
}

現代 class 寫法

class Person {
    constructor(name) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
}

優勢:使用 class 的語法更簡潔、結構化,並支持建構子與繼承等功能,大幅提升程式的可讀性與維護性。

箭頭函式 (=>) 的崛起

為了解決 this 作用域問題,JavaScript 引入了箭頭函式。箭頭函式不僅提供了更短的寫法,還自動綁定 this 到外層作用域,避免了傳統函式內 this 被覆蓋的問題。

範例:

const numbers = [1, 2, 3];

const double = numbers.map(num => num * 2);

console.log(double); // [2, 4, 6]

箭頭函式更適用於回呼函式 (callback) 與高階函式中。

物件與陣列解構 (Destructuring)

解構賦值讓我們可以直接從物件或陣列中解構所需的屬性或元素,而無需冗長的存取。

範例:物件解構

const user = { name: "Alice", age: 30 };

const { name, age } = user;

console.log(name); // Alice

console.log(age); // 30

範例:陣列解構

const colors = ["red", "green", "blue"];

const [first, second] = colors;

console.log(first); // red

console.log(second); // green

私有類別屬性

過去,我們通常用底線(_)來表示類別的私有屬性,但這僅僅是一種慣例,並無法真正做到隱私。自 ES2021 開始,JavaScript 提供了 # 符號來聲明私有屬性,任何在類別外部的存取操作都會被禁止。

範例:


class User {
    #password;

    constructor(password) {
        this.#password = password;    
    }

    validatePassword(input) {    
        return this.#password === input;    
    }
}

const user = new User("secure123");

console.log(user.#password); // SyntaxError,無法存取私有屬性

模組與 import/export

過去,JavaScript 缺乏模組化支持,需要依賴外掛如 CommonJS 或 AMD。如今,原生的 import/export 語法使模組化變得簡單而標準化。

範例:

使用 export 導出模組

export function greet(name) {
    return `Hello, ${name}!`;
}

使用 import 引入模組

import { greet } from './greet.js';

console.log(greet("Alice")); // Hello, Alice!

裝飾器 (Decorator)

裝飾器是為類別或其成員添加元數據或邏輯的一種優雅方式,目前已成為最新標準的一部分。裝飾器在宣告時標記於類別或方法的上方,例如:


function Log(target, name, descriptor) {
    const original = descriptor.value;

    descriptor.value = function(...args) {
        console.log(Calling ${name} with, args);
        return original.apply(this, args);
    };

}

class User {
    @Log

    setPassword(pwd) {
        this.password = pwd;
    }
}

Nullish Coalescing (??)

在處理「空值」時,以往我們多使用 || 運算符,然而這會將其他非預期值如 0、false 視為無效。為此,?? 運算符提供了更精確的解決方式——只會在值為 nullundefined 時觸發。

範例:

const username = null ?? "Guest";

console.log(username); // Guest

從 Callback 到 Promise 再到 async/await

在早期的 JavaScript 中,處理非同步程式碼主要依賴回呼函式(callback)。後來引入的 Promise 提供了更清晰的方式來處理非同步邏輯,但仍需要透過 .then().catch() 來鏈接處理流程。

ES2017 以後,引入了 async/await,進一步簡化非同步程式碼的撰寫,讓它看起來更像同步執行的邏輯:

// 使用 Promise
getData()
    .then(data => console.log(data)) // Data received! after 2 seconds
    .catch(err => console.log(err));

// 使用 async/await
async function fetchData() {
    const data = await getData();
    console.log(data); // Data received! after 2 seconds
}

fetchData();

到了 ES2024 ,更進一步支援了 top-level await

import { doSimething } from '...';

await doSimething();

run();

Map 物件

在 ES6 中,我們也可以使用 Map 物件來儲存鍵值對(key-value pairs)。相較於傳統的物件(Object)只能使用字串或符號作為鍵名(key),Map 物件允許任何型別作為鍵名,甚至包含函式、物件等複雜型別。

以下是一個使用 Map 的範例程式碼:

const myMap = new Map();

// 新增鍵值對
myMap.set('a', 'Apple');
myMap.set('b', 'Banana');
myMap.set('c', 'Cat');

// 取得特定鍵名的值
console.log(myMap.get('a')); // Apple

// 判斷是否包含指定鍵名
console.log(myMap.has('d')); // false

// 移除指定鍵名的鍵值對
myMap.delete('c');

// 迭代 Map 物件中的每個元素,並在回呼函式中處理
myMap.forEach((value, key) => {
    console.log(${key}: ${value});
});

// 輸出:

// a: Apple

// b: Banana

透過 Map 物件,我們可以更彈性地儲存和操作鍵值對,在開發中可以更有效率地達到目的。

Bigint

BigInt 是 JavaScript 提供的一種原生數據型別,用於表示任意精度的整數,這也解決了過去 Number 型別無法準確處理超過 2^53-1 或小於 -(2^53-1) 的值的限制。透過 BigInt,開發者可以安全地進行大型數值的運算,而不必擔心傳統數字型別操作可能導致的精度錯誤。例如:

const largeNumber = 9007199254740991n; // 使用尾部的 'n' 表示 BigInt

const anotherBigInt = BigInt(9007199254740991); // 使用內建 BigInt 函式

console.log(largeNumber + 1n); // 9007199254740992n

console.log(largeNumber * 2n); // 18014398509481982n

這與以前的 JavaScript 不同,因為之前的數值運算會因為精度問題導致計算不準確。如今借助 BigInt,我們可以處理更大範圍的數值並且保持精確,這在需要處理金融計算或科學數據時尤其有用。然而需要注意的是,BigInt 與普通 Number 型別不能直接進行運算,兩者必須通過明確的轉換,以避免運算錯誤。這充分展現了現代 JavaScript 提供更強大的工具來適應多樣化的開發需求。

Symbols 與隱藏屬性

Symbol 用於生成獨一無二且不可變的值。Symbol 最常見的應用場景是作為物件的鍵值,這樣可以避免命名衝突,特別是在撰寫大型專案或與第三方程式庫整合時。例如:

const uniqueKey = Symbol('description');

const myObject = {
    [uniqueKey]: '這是一個隱藏的值'
};

console.log(myObject[uniqueKey]); // "這是一個隱藏的值"

相比於傳統物件屬性,Symbol 提供了一種干預物件屬性訪問的方式,可以有效地保護程式邏輯,避免外部的不當操作。

Intl API

Intl API 是現代 JavaScript 中一個強大的工具,它專門用於國際化需求,例如日期、時間、數字和貨幣的格式化,比傳統方法更加靈活且易於維護。以前,要針對不同的地區格式化日期或數字,通常需要依賴第三方庫或手動實現,而現代的 Intl 類別則大幅簡化了這一過程。例如:

const date = new Date();

const formatter = new Intl.DateTimeFormat('zh-TW', { year: 'numeric', month: 'long', day: 'numeric' });

console.log(formatter.format(date)); // 輸出:2023年10月10日

上述例子展示了如何輕鬆地以台灣地區格式輸出日期,不僅提高了程式的可讀性,還減少了處理國際化的複雜性。Intl API 協助開發者應對全球化的挑戰,是傳統方法無法企及的高效解決方案。

Temporal API 取代原有的 Date

JavaScript 現有的 Date API 從未被視為一個優雅設計的工具。而全新的 Temporal API 提供了強大的工具來操作日期與時間。

範例:

const date = Temporal.Now.plainDateISO();

console.log(date.toString()); // 2025-01-01

結語:駕馭未來的 JavaScript

2025 年的 JavaScript 不僅增加了強大的語法,還進一步提升了開發效率與程式碼的可讀性。無論你是剛起步的新手,還是浸淫多年的開發者,現在都可以享受許多現代化的語言特性。

如果您還想知道更多 Javascript 近年來的詳細改良,觀鹳看夏格飛的文章:

相關文章