HomeLập trìnhJavaScriptCách phân biệt...

Cách phân biệt giữa bản sao sâu và nông trong JavaScript


bởi Lukas Gisder-Dubé

1*CZaCLmeCWGIhvuhKnV7mVg
Ảnh của Oliver Zenglein trên Bapt

Mơi luôn luôn tôt hơn!

Bạn chắc chắn đã xử lý các bản sao trong JavaScript trước đây, ngay cả khi bạn không biết điều đó. Có thể bạn cũng đã nghe nói về mô hình trong lập trình chức năng rằng bạn không nên sửa đổi bất kỳ dữ liệu hiện có nào. Để làm được điều đó, bạn phải biết cách sao chép các giá trị trong JavaScript một cách an toàn. Hôm nay, chúng ta sẽ xem làm thế nào để làm điều này trong khi tránh những cạm bẫy!

Trước hết, một bản sao là gì?

Một bản sao trông giống như thứ cũ, nhưng không phải vậy. Khi bạn thay đổi bản sao, bạn mong muốn bản gốc giữ nguyên, trong khi bản sao thay đổi.

Trong lập trình, chúng tôi lưu trữ các giá trị trong các biến. Tạo một bản sao có nghĩa là bạn khởi tạo một biến mới có cùng (các) giá trị. Tuy nhiên, có một cạm bẫy tiềm ẩn lớn cần xem xét: sao chép sâu so với sao chép nông. Một bản sao sâu có nghĩa là tất cả các giá trị của biến mới được sao chép và bị ngắt kết nối với bản gốc Biến đổi. Một bản sao nông có nghĩa là các giá trị (phụ) nhất định là vẫn kết nối vào biến ban đầu.

Để thực sự hiểu về sao chép, bạn phải tìm hiểu cách JavaScript lưu trữ các giá trị.

Các kiểu dữ liệu nguyên thủy

Các kiểu dữ liệu nguyên thủy bao gồm:

  • Số – ví dụ 1
  • Chuỗi – ví dụ 'Hello'
  • Boolean – ví dụ true
  • undefined
  • null

Khi bạn tạo các giá trị này, chúng sẽ được kết hợp chặt chẽ với biến mà chúng được gán. Chúng chỉ tồn tại một lần. Điều đó có nghĩa là bạn không thực sự phải lo lắng về việc sao chép các kiểu dữ liệu nguyên thủy trong JavaScript. Khi bạn tạo một bản sao, nó sẽ là một bản sao thực sự. Hãy xem một ví dụ:

const a = 5
let b = a // this is the copy
b = 6
console.log(b) // 6
console.log(a) // 5

Bằng cách thực hiện b = a , bạn tạo bản sao. Bây giờ, khi bạn gán lại một giá trị mới cho bgiá trị của b thay đổi, nhưng không phải của a.

Các kiểu dữ liệu tổng hợp — Đối tượng và Mảng

Về mặt kỹ thuật, mảng cũng là đối tượng, vì vậy chúng hoạt động theo cùng một cách. Tôi sẽ đi qua cả hai chi tiết sau.

Đọc thêm  Định dạng chuỗi JavaScript – Định dạng chuỗi trong JS

Ở đây nó trở nên thú vị hơn. Các giá trị này thực sự chỉ được lưu trữ một lần khi được khởi tạo và việc gán một biến chỉ tạo một con trỏ (tham chiếu) tới giá trị đó.

Bây giờ, nếu chúng ta tạo một bản sao b = a và thay đổi một số giá trị lồng nhau trong bnó thực sự thay đổi agiá trị lồng nhau của cũng vậy, vì ab thực sự chỉ ra cùng một điều. Ví dụ:

const a = {
  en: 'Hello',
  de: 'Hallo',
  es: 'Hola',
  pt: 'Olà'
}
let b = a
b.pt="Oi"
console.log(b.pt) // Oi
console.log(a.pt) // Oi

Trong ví dụ trên, chúng tôi thực sự đã thực hiện một bản sao nông. Điều này đôi khi có vấn đề, vì chúng tôi mong muốn biến cũ có giá trị ban đầu chứ không phải giá trị đã thay đổi. Khi chúng tôi truy cập nó, đôi khi chúng tôi gặp lỗi. Có thể xảy ra trường hợp bạn thử gỡ lỗi một lúc trước khi tìm ra lỗi, vì nhiều nhà phát triển không thực sự nắm bắt được khái niệm này và không cho rằng đó là lỗi.

1*niSsr1dxva2RWXrvHT8hxg
Ảnh của Thomas Millot trên Bapt

Hãy xem cách chúng ta có thể tạo bản sao của các đối tượng và mảng.

Các đối tượng

Có nhiều cách để tạo bản sao của các đối tượng, đặc biệt là với đặc tả JavaScript mở rộng và cải tiến mới.

Toán tử trải rộng

Được giới thiệu với ES2015, toán tử này rất tuyệt vời vì nó rất ngắn và đơn giản. Nó ‘phân tán’ tất cả các giá trị vào một đối tượng mới. Bạn có thể sử dụng nó như sau:

const a = {
  en: 'Bye',
  de: 'Tschüss'
}
let b = {...a}
b.de="Ciao"
console.log(b.de) // Ciao
console.log(a.de) // Tschüss

Bạn cũng có thể dùng nó để gộp hai đối tượng lại với nhau chẳng hạn const c = {...a, ...b} .

Đối tượng.gán

Điều này chủ yếu được sử dụng trước khi toán tử trải rộng xuất hiện và về cơ bản nó cũng làm điều tương tự. Tuy nhiên, bạn phải cẩn thận, vì lập luận đầu tiên trong Object.assign() phương pháp thực sự được sửa đổi và trả lại. Vì vậy, hãy đảm bảo rằng bạn chuyển đối tượng cần sao chép ít nhất là đối số thứ hai. Thông thường, bạn sẽ chỉ chuyển một đối tượng trống làm đối số đầu tiên để ngăn việc sửa đổi bất kỳ dữ liệu hiện có nào.

const a = {
  en: 'Bye',
  de: 'Tschüss'
}
let b = Object.assign({}, a)
b.de="Ciao"
console.log(b.de) // Ciao
console.log(a.de) // Tschüss

Cạm bẫy: Đối tượng lồng nhau

Như đã đề cập trước đây, có một cảnh báo lớn khi xử lý các đối tượng sao chép, áp dụng cho cả hai phương pháp được liệt kê ở trên. Khi bạn có một đối tượng (hoặc mảng) lồng nhau và bạn sao chép nó, các đối tượng lồng bên trong đối tượng đó sẽ không được sao chép, vì chúng chỉ là con trỏ/tham chiếu. Do đó, nếu bạn thay đổi đối tượng lồng nhau, bạn sẽ thay đổi nó cho cả hai trường hợp, nghĩa là cuối cùng bạn sẽ thực hiện một sao chép nông một lần nữa. Ví dụ: // VÍ DỤ XẤU

const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = {...a}
b.foods.dinner="Soup" // changes for both objects
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Soup

để làm một bản sao sâu của các đối tượng lồng nhau, bạn sẽ phải xem xét điều đó. Một cách để ngăn chặn điều đó là sao chép thủ công tất cả các đối tượng lồng nhau:

const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = {foods: {...a.foods}}
b.foods.dinner="Soup"
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Trong trường hợp bạn đang tự hỏi phải làm gì khi đối tượng có nhiều phím hơn chỉ foods , bạn có thể sử dụng toàn bộ tiềm năng của toán tử trải rộng. Khi chuyển nhiều thuộc tính hơn sau ...spread chúng ghi đè lên các giá trị ban đầu, ví dụ const b = {...a, foods: {...a.foods}} .

Tạo bản sao sâu mà không cần suy nghĩ

Điều gì sẽ xảy ra nếu bạn không biết độ sâu của các cấu trúc lồng nhau? Có thể rất tẻ nhạt khi đi qua các đối tượng lớn theo cách thủ công và sao chép mọi đối tượng lồng nhau bằng tay. Có một cách để sao chép mọi thứ mà không cần suy nghĩ. Bạn đơn giản stringify đối tượng của bạn và parse nó ngay sau:

const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner="Soup"
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Ở đây, bạn phải cân nhắc rằng bạn sẽ không thể sao chép các thể hiện của lớp tùy chỉnh, vì vậy bạn chỉ có thể sử dụng nó khi sao chép các đối tượng bằng giá trị JavaScript gốc phía trong.

Đọc thêm  Học JavaScript bằng cách tạo trò chơi Tetris
1*hepu5hNOaAqnhE60z-oicA
Ảnh của Robert Zunikoff trên Bapt

Mảng

Sao chép mảng cũng phổ biến như sao chép đối tượng. Rất nhiều logic đằng sau nó là tương tự, vì các mảng cũng chỉ là các đối tượng bên trong.

Toán tử trải rộng

Đối với các đối tượng, bạn có thể sử dụng toán tử trải rộng để sao chép một mảng:

const a = [1,2,3]
let b = [...a]
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

Hàm mảng — ánh xạ, lọc, giảm

Các phương thức này sẽ trả về một mảng mới với tất cả (hoặc một số) giá trị của mảng ban đầu. Trong khi làm điều đó, bạn cũng có thể sửa đổi các giá trị, điều này rất hữu ích:

const a = [1,2,3]
let b = a.map(el => el)
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

Ngoài ra, bạn có thể thay đổi thành phần mong muốn trong khi sao chép:

const a = [1,2,3]
const b = a.map((el, index) => index === 1 ? 4 : el)
console.log(b[1]) // 4
console.log(a[1]) // 2

Array.slice

Phương thức này thường được sử dụng để trả về một tập hợp con của các phần tử, bắt đầu từ một chỉ mục cụ thể và tùy ý kết thúc tại một chỉ mục cụ thể của mảng ban đầu. Khi đang sử dụng array.slice() hoặc array.slice(0) bạn sẽ nhận được một bản sao của mảng ban đầu.

const a = [1,2,3]
let b = a.slice(0)
b[1] = 4
console.log(b[1]) // 4
console.log(a[1]) // 2

mảng lồng nhau

Tương tự đối với các đối tượng, sử dụng các phương thức trên để sao chép một mảng với một mảng hoặc đối tượng khác bên trong sẽ tạo ra một bản sao nông. Để ngăn chặn điều đó, cũng sử dụng JSON.parse(JSON.stringify(someArray)) .

THƯỞNG: sao chép phiên bản của các lớp tùy chỉnh

Khi bạn đã là một chuyên gia về JavaScript và bạn xử lý các hàm hoặc lớp hàm tạo tùy chỉnh của mình, có thể bạn cũng muốn sao chép các phiên bản của chúng.

Đọc thêm  Giải thích phương thức JavaScript Math.random()

Như đã đề cập trước đây, bạn không thể chỉ xâu chuỗi + phân tích cú pháp những thứ đó, vì bạn sẽ mất các phương thức lớp của mình. Thay vào đó, bạn sẽ muốn thêm một tùy chỉnh copy để tạo một thể hiện mới với tất cả các giá trị cũ. Hãy xem cách nó hoạt động:

class Counter {
  constructor() {
     this.count = 5
  }
  copy() {
    const copy = new Counter()
    copy.count = this.count
    return copy
  }
}
const originalCounter = new Counter()
const copiedCounter = originalCounter.copy()
console.log(originalCounter.count) // 5
console.log(copiedCounter.count) // 5
copiedCounter.count = 7
console.log(originalCounter.count) // 5
console.log(copiedCounter.count) // 7

Để xử lý các đối tượng và mảng được tham chiếu bên trong thể hiện của bạn, bạn sẽ phải áp dụng các kỹ năng mới học của mình về sao chép sâu! Tôi sẽ chỉ thêm một giải pháp cuối cùng cho hàm tạo tùy chỉnh copy phương pháp để làm cho nó năng động hơn:

Với phương pháp sao chép đó, bạn có thể đặt bao nhiêu giá trị tùy thích vào hàm tạo của mình mà không cần phải sao chép mọi thứ theo cách thủ công!

Giới thiệu về tác giả: Lukas Gisder-Dubé đồng sáng lập và lãnh đạo một công ty khởi nghiệp với tư cách là CTO trong 1 năm rưỡi, xây dựng đội ngũ công nghệ và kiến ​​trúc. Sau khi rời công ty khởi nghiệp, anh ấy đã dạy mã hóa với tư cách là Người hướng dẫn chính tại Ironhack và hiện đang xây dựng Cơ quan tư vấn & Đại lý khởi nghiệp ở Berlin. Kiểm tra dube.io để tìm hiểu thêm.

1*p-l0Cee1IHvX0RQkVTOceQ



Zik.vn – Biên dịch & Biên soạn Lại

spot_img

Create a website from scratch

Just drag and drop elements in a page to get started with Newspaper Theme.

Buy Now ⟶

Bài viết liên quang

DMCA.com Protection Status