Home >Web Front-end >JS Tutorial >Clean Code: JavaScript immutability, core concepts and tools

Clean Code: JavaScript immutability, core concepts and tools

Susan Sarandon
Susan SarandonOriginal
2025-01-17 20:33:15527browse

Clean Code: JavaScript immutability, core concepts and tools

What is mutation?

Mutation refers to directly modifying an existing value. In JavaScript, objects and arrays can be changed (mutated) by default:

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

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

These mutations can create hard-to-find bugs, especially in large applications.

Why should mutations be avoided?

Let’s look at a simple example:

<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>

Problem of mutation:

  1. Shared references: Different parts of the code can change the same object without other parts knowing.
  2. Side effects: Changes will affect other functions that use the same object.
  3. Difficult to debug: Can't trace which part of the code changed the object.
  4. Complex tests: Mutations make unit tests harder to write.

Solution: Immutability Programming

The immutability method creates a new copy of the object for each change:

<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>

Benefits of this approach:

  1. Predictability: every function returns a new state, no hidden side effects.
  2. Change Tracking: Every change creates a new object that can be tracked.
  3. Easy to test: Functions are pure functions and easier to test.
  4. Better debugging: can compare status before and after changes.

Modern Immutability Tools

Immer: Simple writing style

Immer allows you to write code that looks like normal JavaScript code, but produces unchanged results:

<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>

Benefits of Immer:

  • Familiar syntax: Write code like you normally would.
  • No new API to learn: use regular JavaScript objects and arrays.
  • Quick: Copy only the changed parts.
  • Automatic change detection: Track changes and create new references only when needed.
  • Works well with TypeScript: all type information is preserved.

Immutable.js: efficient data structure

Immutable.js provides data structures designed for immutability:

<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>

Benefits of Immutable.js:

  • Using immutable data structures is fast.
  • Rich API for working with data.
  • Memory efficient data sharing.
  • Use equals() for easy equality checking.
  • Prevent accidental changes.

ESLint’s immutable configuration

ESLint can help enforce immutable coding practices through specific rules:

<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>

These rules will:

  • Prevent direct data mutation.
  • Encourage the use of const instead of let.
  • It is recommended to use readonly types in TypeScript.

TypeScript and immutability

TypeScript helps enforce immutability through its type system:

<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>

Read-only modifier for TypeScript:

  • readonly: Prevent attribute changes.
  • ReadonlyArray: Prevent array changes.
  • Readonly: Makes all properties read-only.

These types are checked at compile time, helping to catch errors early.

Conclusion

Immutability makes your code more predictable and maintainable. While it takes some getting used to, the advantages in reliability and maintainability are worth it.

The above is the detailed content of Clean Code: JavaScript immutability, core concepts and tools. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn