LazyCodet

a

03:05:08 6/3/2025 - 0 views -

Spread ... | Toán tử lan tỏa

Tôi sẽ giải thích một cách toàn diện về toán tử spread (...) trong JavaScript, đặc biệt liên quan đến việc sao chép (copy) và các ứng dụng khác của nó. Tôi sẽ trình bày chi tiết bằng tiếng Việt, kèm ví dụ và lưu ý quan trọng.


1. Toán tử Spread là gì?

Toán tử spread (...) được giới thiệu trong ES6 (ES2015), cho phép "lan tỏa" (spread) các phần tử của một iterable (như mảng, chuỗi) hoặc các thuộc tính của một object thành các phần tử riêng lẻ. Nó thường được dùng để:

  • Sao chép (copy) mảng hoặc object.
  • Hợp nhất (merge) các mảng hoặc object.
  • Truyền tham số vào hàm.

2. Sao chép (Copy) với Toán tử Spread

Toán tử spread thường được dùng để tạo shallow copy (bản sao nông) của mảng hoặc object.

a. Sao chép Mảng

const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // Sao chép arr1 sang arr2

arr2.push(4); // Thay đổi arr2 không ảnh hưởng arr1
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3, 4]
  • Lưu ý: Đây là shallow copy, chỉ sao chép cấp đầu tiên. Nếu mảng chứa object hoặc mảng con, các phần tử đó vẫn là tham chiếu:
    const arr1 = [1, { a: 2 }];
    const arr2 = [...arr1];
    
    arr2[1].a = 3; // Thay đổi object trong arr2 cũng ảnh hưởng arr1
    console.log(arr1); // [1, { a: 3 }]
    console.log(arr2); // [1, { a: 3 }]

b. Sao chép Object

const obj1 = { name: "Nguyễn Văn A", age: 25 };
const obj2 = { ...obj1 }; // Sao chép obj1 sang obj2

obj2.age = 26; // Thay đổi obj2 không ảnh hưởng obj1
console.log(obj1); // { name: "Nguyễn Văn A", age: 25 }
console.log(obj2); // { name: "Nguyễn Văn A", age: 26 }
  • Lưu ý: Tương tự mảng, nếu object có thuộc tính là object lồng nhau, bản sao vẫn giữ tham chiếu:
    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = { ...obj1 };
    
    obj2.b.c = 3; // Thay đổi b.c trong obj2 cũng ảnh hưởng obj1
    console.log(obj1); // { a: 1, b: { c: 3 } }
    console.log(obj2); // { a: 1, b: { c: 3 } }

3. Các ứng dụng khác của Toán tử Spread

Ngoài sao chép, toán tử spread còn có nhiều ứng dụng hữu ích:

a. Hợp nhất (Merge) Mảng

const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4]

b. Hợp nhất Object

const obj1 = { name: "Nguyễn Văn A" };
const obj2 = { age: 25 };
const merged = { ...obj1, ...obj2 };
console.log(merged); // { name: "Nguyễn Văn A", age: 25 }
  • Lưu ý: Nếu có thuộc tính trùng nhau, giá trị sau sẽ ghi đè giá trị trước:
    const obj1 = { key: 1 };
    const obj2 = { key: 2 };
    const merged = { ...obj1, ...obj2 };
    console.log(merged); // { key: 2 }

c. Truyền tham số vào hàm

function sum(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

d. Chuyển chuỗi thành mảng ký tự

const str = "Hello";
const chars = [...str];
console.log(chars); // ["H", "e", "l", "l", "o"]

e. Thêm phần tử vào mảng mà không thay đổi mảng gốc

const arr = [1, 2];
const newArr = [...arr, 3];
console.log(arr); // [1, 2]
console.log(newArr); // [1, 2, 3]

4. So sánh với các phương pháp sao chép khác

a. Spread vs slice() (dành cho mảng)

  • slice() chỉ dùng cho mảng:
    const arr1 = [1, 2, 3];
      const arr2 = arr1.slice();
  • Spread linh hoạt hơn vì áp dụng được cho cả object.

b. Spread vs Object.assign() (dành cho object)

  • Object.assign():
    const obj1 = { a: 1 };
      const obj2 = Object.assign({}, obj1);
  • Spread ngắn gọn hơn và dễ đọc hơn.

c. Spread vs Deep Copy

  • Spread chỉ tạo shallow copy. Để tạo deep copy (sao chép toàn bộ, bao gồm các cấp lồng nhau), cần dùng phương pháp khác như JSON.parse(JSON.stringify()):
    const obj1 = { a: 1, b: { c: 2 } };
      const deepCopy = JSON.parse(JSON.stringify(obj1));
      deepCopy.b.c = 3;
      console.log(obj1); // { a: 1, b: { c: 2 } }

5. Hạn chế của Toán tử Spread

  • Shallow Copy: Như đã đề cập, spread không sao chép sâu (deep copy), nên các object hoặc mảng lồng nhau vẫn giữ tham chiếu.
  • Không sao chép prototype: Spread chỉ sao chép các thuộc tính "own" (thuộc tính trực tiếp) của object, không sao chép các thuộc tính từ prototype:
    function Person() { this.name = "A"; }
      Person.prototype.age = 25;
      const p1 = new Person();
      const p2 = { ...p1 };
      console.log(p2.age); // undefined (không sao chép age từ prototype)
  • Hiệu suất: Với dữ liệu lớn, spread có thể chậm hơn các phương pháp khác như vòng lặp thủ công.

6. Lưu ý khi sử dụng

  • Spread hoạt động với iterable (mảng, chuỗi, Set, v.v.) và object. Nó không hoạt động với giá trị nguyên thủy như số hoặc null:
    const num = 123;
      console.log([...num]); // TypeError: num is not iterable
  • Khi dùng với object, spread chỉ áp dụng được từ ES2018 (ES9). Trước đó, bạn phải dùng Object.assign().

7. Ứng dụng thực tế

  • Redux/State Management: Sao chép state mà không làm thay đổi state gốc:
    const state = { count: 1 };
      const newState = { ...state, count: state.count + 1 };
  • Xử lý dữ liệu API: Hợp nhất dữ liệu từ nhiều nguồn:
    const defaults = { timeout: 5000 };
      const options = { ...defaults, url: "https://api.com" };
  • Clone nhanh: Tạo bản sao của mảng/object để thao tác mà không ảnh hưởng gốc.

Nếu bạn muốn tôi giải thích thêm về bất kỳ phần nào (ví dụ: shallow copy vs deep copy, cách tối ưu hiệu suất, hoặc ứng dụng cụ thể), hãy cho tôi biết nhé! Bạn cũng có thể yêu cầu ví dụ thực tế hoặc bài tập để thực hành.