首頁 >web前端 >js教程 >乾淨的程式碼:JavaScript 不變性、核心概念與工具

乾淨的程式碼:JavaScript 不變性、核心概念與工具

Susan Sarandon
Susan Sarandon原創
2025-01-17 20:33:15527瀏覽

Clean Code: JavaScript immutability, core concepts and tools

什麼是變異?

變異是指直接修改已存在的數值。在 JavaScript 中,物件和陣列預設可以更改(變異):

<code class="language-javascript">// 变异示例
const user = { name: 'Alice' };
user.name = 'Bob';           // 变异对象属性

const numbers = [1, 2, 3];
numbers.push(4);             // 变异数组
numbers[0] = 0;              // 变异数组元素</code>

這些變異可能會產生難以發現的錯誤,尤其是在大型應用程式中。

為什麼要避免變異?

讓我們來看一個簡單的例子:

<code class="language-javascript">// 使用变异的代码
const cart = {
  items: [],
  total: 0
};

function addProduct(cart, product) {
  cart.items.push(product);
  cart.total += product.price;
}

// 使用示例
const myCart = cart;
addProduct(myCart, { id: 1, name: "Laptop", price: 999 });
// 直接更改 myCart
console.log(cart === myCart); // true,两个变量指向同一个对象</code>

變異的問題:

  1. 共享引用:程式碼的不同部分可以更改同一個對象,而其他部分並不知情。
  2. 副作用:改變會影響使用同一個物件的其它函數。
  3. 難以調試:無法追蹤程式碼的哪個部分更改了物件。
  4. 複雜的測試:變異使單元測試更難編寫。

解決方案:不變性程式

不變性方法為每次變更建立新的物件副本:

<code class="language-javascript">// 不变性代码
function addProduct(cart, product) {
  // 创建一个新对象,而不更改原始对象
  return {
    items: [...cart.items, product],
    total: cart.total + product.price
  };
}

// 使用示例
const initialCart = { items: [], total: 0 };
const newCart = addProduct(initialCart, { id: 1, name: "Laptop", price: 999 });

console.log(initialCart); // { items: [], total: 0 }
console.log(newCart);     // { items: [{...}], total: 999 }
console.log(initialCart === newCart); // false,它们是不同的对象</code>

這種方法的好處:

  1. 可預測性:每個函數傳回一個新的狀態,沒有隱藏的副作用。
  2. 更改追蹤:每次更改都會建立一個新的對象,可以對其進行追蹤。
  3. 易於測試:函數是純函數,更容易測試。
  4. 更好的調試:可以比較更改前後的狀態。

現代不變性工具

Immer:簡單的寫作風格

Immer 允許您編寫看起來像普通 JavaScript 程式碼的程式碼,但會產生不變的結果:

<code class="language-javascript">import produce from 'immer';

const initialCart = {
  items: [],
  total: 0,
  customer: {
    name: 'Alice',
    preferences: {
      notifications: true
    }
  }
};

// 不使用 Immer(冗长的方法)
const updatedCart = {
  ...initialCart,
  items: [...initialCart.items, { id: 1, name: "Laptop", price: 999 }],
  total: initialCart.total + 999,
  customer: {
    ...initialCart.customer,
    preferences: {
      ...initialCart.customer.preferences,
      notifications: false
    }
  }
};

// 使用 Immer(简单的方法)
const updatedCartImmer = produce(initialCart, draft => {
  draft.items.push({ id: 1, name: "Laptop", price: 999 });
  draft.total += 999;
  draft.customer.preferences.notifications = false;
});</code>

Immer 的好處:

  • 熟悉的語法:像平常一樣寫程式碼。
  • 無需學習新的 API:使用常規的 JavaScript 物件和陣列。
  • 快速:只複製更改的部分。
  • 自動更改偵測:追蹤更改,僅在需要時建立新的參考。
  • 與 TypeScript 配合良好:保留所有類型資訊。

Immutable.js:高效率的資料結構

Immutable.js 提供了專為不變性設計的資料結構:

<code class="language-javascript">import { Map, List } from 'immutable';

// 创建不变的数据结构
const cartState = Map({
  items: List([]),
  total: 0
});

// 添加一个项目
const newCart = cartState
  .updateIn(
    ['items'],
    items => items.push(Map({
      id: 1,
      name: "Laptop",
      price: 999
    }))
  )
  .update('total', total => total + 999);

// Immutable.js 方法始终返回新实例
console.log(cartState.getIn(['items']).size); // 0
console.log(newCart.getIn(['items']).size);   // 1

// 轻松比较
console.log(cartState.equals(newCart)); // false

// 转换回常规 JavaScript
const cartJS = newCart.toJS();</code>

Immutable.js 的好處:

  • 使用不變的資料結構速度快。
  • 用於處理資料的豐富 API。
  • 記憶體高效的資料共享。
  • 使用 equals() 進行輕鬆的相等性檢查。
  • 防止意外更改。

ESLint 的不變性配置

ESLint 可以透過特定的規則來幫助強制執行不變的編碼實踐:

<code class="language-javascript">// .eslintrc.js
module.exports = {
  plugins: ['functional'],
  rules: {
    'functional/immutable-data': 'error',
    'functional/no-let': 'error',
    'functional/prefer-readonly-type': 'error'
  }
};</code>

這些規則將:

  • 防止直接資料變異。
  • 鼓勵使用 const 而不是 let。
  • 建議在 TypeScript 中使用 readonly 類型。

TypeScript 和不變性

TypeScript 透過其型別系統幫助強制執行不變性:

<code class="language-typescript">// 购物车的不变类型
type Product = {
  readonly id: number;
  readonly name: string;
  readonly price: number;
};

type Cart = {
  readonly items: ReadonlyArray<Product>;
  readonly total: number;
};

// TypeScript 防止变异
const cart: Cart = {
  items: [],
  total: 0
};

// 编译错误:items 是只读的
cart.items.push({ id: 1, name: "Laptop", price: 999 });

// 函数必须创建一个新的购物车
function addProduct(cart: Cart, product: Product): Cart {
  return {
    items: [...cart.items, product],
    total: cart.total + product.price
  };
}

// TypeScript 确保原始对象不会更改
const newCart = addProduct(cart, { id: 1, name: "Laptop", price: 999 });</code>

TypeScript 的唯讀修飾符:

  • readonly:防止屬性變更。
  • ReadonlyArray:防止陣列更改。
  • Readonly:使所有屬性變成唯讀。

這些類型在編譯時會進行檢查,有助於儘早發現錯誤。

結論

不變性使您的程式碼更易於預測和維護。雖然需要一些時間來適應,但在可靠性和可維護性方面的優勢是值得的。

以上是乾淨的程式碼:JavaScript 不變性、核心概念與工具的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn