0
0
Lập trình
Admin Team
Admin Teamtechmely

Hiểu biết về Tính khả biến trong JavaScript

Đăng vào 4 tháng trước

• 8 phút đọc

Giới thiệu

Bạn đã bao giờ tự hỏi điều gì xảy ra bên trong máy tính khi bạn tạo và biến đổi các biến? Sự khác biệt giữa letconst là gì? Tại sao bạn có thể thay đổi một đối tượng hoặc mảng ngay cả khi đã khai báo nó bằng const?

Trong bài viết này, tôi sẽ phân tích tất cả những khái niệm này, tập trung vào tính khả biến của các loại dữ liệu khác nhau với các sơ đồ đơn giản để minh họa cách mà các biến hoạt động trong JavaScript.

Tính khả biến là gì?

Tính khả biến (mutability) đề cập đến việc liệu giá trị của một loại dữ liệu có thể thay đổi sau khi đã được tạo hay không. Đây là một khái niệm cơ bản phân chia hai loại chính của dữ liệu trong JavaScript: các loại dữ liệu nguyên thủy và các loại đối tượng.

Để hiểu rõ hơn về khái niệm này, hãy dành chút thời gian để hiểu các biến thực sự là gì và chúng hoạt động như thế nào trong JavaScript.

Biến là gì?

Trong JavaScript hiện đại, giá trị được tạo ra và lưu trữ dưới dạng một biến sử dụng từ khóa letconst. Hai từ khóa này đều hoạt động như những con trỏ đến một vị trí trong bộ nhớ máy tính nơi giá trị được lưu trữ. Sự khác biệt duy nhất là độ linh hoạt của chúng. let là linh hoạt vì nó có thể được di chuyển để trỏ đến một vị trí khác trong bộ nhớ, khác với vị trí ban đầu, trong khi const chỉ có thể trỏ đến địa chỉ mà nó đã được khởi tạo.

Bây giờ, hãy quay lại để hiểu tính khả biến của các loại dữ liệu nguyên thủy và đối tượng trong JavaScript.

Tính khả biến của các loại dữ liệu nguyên thủy

Các loại dữ liệu nguyên thủy là immutable, có nghĩa là giá trị của chúng không thể bị thay đổi sau khi được tạo ra. Khi bạn thực hiện một phép toán trên một nguyên thủy, bạn không thay đổi giá trị gốc mà chỉ đơn giản là tạo ra một giá trị mới.

Một số loại dữ liệu nguyên thủy phổ biến bao gồm:

  • Chuỗi (String)
  • Số (Number)
  • Boolean
  • Null
  • Undefined

Hãy xem một vài ví dụ để hiểu rõ hơn:

javascript Copy
let x = 5;
x = 10;
console.log(x); // 10

Từ đoạn mã trên, bạn có thể nghĩ rằng “chúng ta đã biến đổi giá trị của x từ 5 thành 10?” Thực tế, chúng ta không làm vậy.

Điều chúng ta đã làm là di chuyển con trỏ từ vị trí trong bộ nhớ máy tính lưu trữ giá trị 5 đến một vị trí khác lưu trữ giá trị 10. Bạn sẽ nhận thấy rằng giá trị 5 vẫn được lưu trữ ở vị trí ban đầu nhưng giờ đây không có gì trỏ đến nó, vì vậy nó sẽ bị xóa bởi bộ thu gom rác của JavaScript để tạo không gian cho một giá trị khác.

Điều gì sẽ xảy ra khi chúng ta thử điều này với biến const?

javascript Copy
const x = 5;
x = 10; // Uncaught TypeError: Assignment to constant variable.
console.log(x); // Điều này sẽ không chạy

Khi chúng ta cố gắng di chuyển con trỏ đến một vị trí khác, chúng ta gặp lỗi vì các biến được khởi tạo bằng const không thể được di chuyển đến một vị trí khác trong bộ nhớ và vì các nguyên thủy là immutable, không có cách nào để chúng ta thay đổi giá trị 5 thành một giá trị khác.

Dưới đây là một ví dụ khác với chuỗi:

javascript Copy
let msg = "hello";
msg.toUpperCase(); // Phương thức này trả về một chuỗi mới "HELLO"
console.log(msg); // Chuỗi gốc vẫn là "hello"

Từ ví dụ trên, bạn có thể mong đợi biến msg sẽ trở thành chữ hoa nhưng nó sẽ không vì tính bất biến của chuỗi. Tuy nhiên, phương thức toUpperCase() trả về một giá trị chuỗi mới với tất cả các ký tự viết hoa mà chúng ta có thể gán lại cho biến msg.

javascript Copy
let msg = "hello";
msg = msg.toUpperCase(); // Giá trị trả về được gán lại cho msg
console.log(msg); // "HELLO"

Dưới đây là một sơ đồ để giúp minh họa:

Bạn sẽ thấy rằng chuỗi gốc “hello” không bị thay đổi nhưng một chuỗi mới với tất cả các ký tự viết hoa đã được tạo ra và con trỏ msg đã được di chuyển đến vị trí mới lưu trữ giá trị đã viết hoa.

Tính khả biến của các loại đối tượng

Các loại đối tượng là mutable, có nghĩa là các thuộc tính của chúng có thể được thay đổi mà không cần phải gán lại biến. Điều này bao gồm:

  • Đối tượng (Objects)
  • Mảng (Arrays)
  • Hàm (Functions)

Khi bạn có một biến chứa một đối tượng, nó không chứa chính đối tượng đó, mà là một tham chiếu đến vị trí của nó trong bộ nhớ máy tính. Hãy tưởng tượng nó như một nhãn trên một hộp lưu trữ. Biến là nhãn, và đối tượng là nội dung bên trong hộp.

Vì các đối tượng có tính khả biến, bạn có thể thay đổi nội dung của chúng trực tiếp mà không cần thay đổi hoặc di chuyển nhãn.

Xem ví dụ dưới đây:

javascript Copy
let bio = { name: "John", age: 30 };
console.log(bio.age); // 30

bio.age = 50; // Chúng ta đang thay đổi trực tiếp một thuộc tính trên bio
console.log(bio.age); // Điều này sẽ log 50!

Từ ví dụ trên, chúng ta có thể thấy rằng việc sửa đổi các thuộc tính của đối tượng trực tiếp thay đổi dữ liệu tại vị trí bộ nhớ đó. Chúng ta không cần phải gán lại biến bio đến một vị trí khác, chúng ta chỉ đơn giản là thay đổi giá trị mà nó đang trỏ đến.

Sơ đồ dưới đây giúp minh họa điều đó:

Biến bio trỏ đến cùng một địa chỉ trong cả hai hình, nhưng nội dung bên trong đã thay đổi. Không quan trọng nếu chúng ta sử dụng let hay const để khởi tạo biến. Vì chúng ta không di chuyển con trỏ, việc thay đổi nội dung của đối tượng vẫn hoạt động giống nhau.

Nơi chúng ta sẽ gặp vấn đề là khi chúng ta khởi tạo biến với const và sau đó cố gắng gán nó cho một giá trị khác (trỏ đến một vị trí khác):

javascript Copy
const bio = { name: "John", age: 30 };
bio = { name: "John", age: 50 }; // Gây lỗi
console.log(bio.age); // Điều này sẽ không chạy

Ở đây, chúng ta đã cố gắng gán bio cho một đối tượng khác nhưng gặp lỗi, vì bio đã được khởi tạo với const. Chúng ta sẽ gặp phải lỗi tương tự nếu chúng ta cố gắng gán lại nó cho một loại nguyên thủy. Điều này sẽ hoạt động tốt nếu chúng ta khởi tạo biến với let và sau đó gán lại cho một giá trị khác bất kể loại dữ liệu chúng ta đang gán lại là gì.

Hãy xem ví dụ khác với một mảng, đây là một loại đối tượng:

javascript Copy
const colors = ['red', 'yellow', 'blue'];

/*  Chúng ta có thể thêm hoặc xóa mục trực tiếp từ mảng 'colors'
 bằng các phương thức thay đổi nó, như .push() hoặc .pop() */

colors.push('green');
console.log(colors); // Output: ['red', 'yellow', 'blue', 'green']

Tương tự như biến bio, chúng ta không di chuyển con trỏ của colors nhưng đã thay đổi nội dung của địa chỉ mà nó đang trỏ đến bằng cách sử dụng phương thức .push(). Đây là bản chất của tính khả biến: khả năng thay đổi nội dung của một cấu trúc dữ liệu mà không cần tạo ra một cái mới.

Thực tiễn tốt nhất

  • Sử dụng const cho các biến không cần thay đổi: Hãy sử dụng const cho các biến mà bạn không muốn thay đổi giá trị để đảm bảo tính an toàn và giúp code dễ hiểu hơn.
  • Sử dụng let cho các biến có thể thay đổi: Chỉ sử dụng let khi bạn thực sự cần khả năng thay đổi giá trị của biến.

Cạm bẫy phổ biến

  • Lẫn lộn giữa letconst: Nhiều lập trình viên mới thường sử dụng let quá nhiều. Hãy cân nhắc việc sử dụng const trước khi quyết định sử dụng let.
  • Không hiểu rõ về tính khả biến: Điều này có thể dẫn đến nhiều lỗi không mong muốn. Hãy đảm bảo bạn hiểu rõ cách hoạt động của các loại dữ liệu mà bạn sử dụng.

Mẹo hiệu suất

  • Tránh sử dụng nhiều biến toàn cục: Sử dụng biến cục bộ thay vì biến toàn cục giúp cải thiện hiệu suất và giảm thiểu các lỗi không mong muốn.
  • Sử dụng các cấu trúc dữ liệu phù hợp: Chọn loại dữ liệu phù hợp cho các tình huống cụ thể có thể giúp cải thiện hiệu suất.

Khắc phục sự cố

Nếu bạn gặp lỗi như TypeError: Assignment to constant variable, điều này có thể do bạn đang cố gắng gán lại một biến đã được khai báo bằng const. Hãy kiểm tra lại mã của bạn để đảm bảo rằng bạn không cố gắng thay đổi giá trị của biến const.

Kết luận

Hiểu biết về tính khả biến là một cột mốc quan trọng trong hành trình của bất kỳ lập trình viên JavaScript nào. Bây giờ, bạn nên có một hiểu biết rõ ràng rằng các nguyên thủy là immutable; các phép toán trên chúng tạo ra các giá trị mới trong khi các đối tượng là mutable, cho phép bạn thay đổi nội dung của chúng trực tiếp.

Các biến là các con trỏ và khai báo một biến với let có nghĩa là bạn có thể trỏ nó đến một vị trí khác trong bộ nhớ sau khi tạo ra, nhưng các biến được khai báo bằng const thì không thể di chuyển và luôn trỏ đến cùng một vị trí trong bộ nhớ.

Trong các bài viết tiếp theo, tôi sẽ khám phá các khái niệm khác như gán theo giá trịgán theo tham chiếu và áp dụng cách tiếp cận ưu tiên bất biến, đặc biệt với các đối tượng và mảng, để ngăn chặn các hiệu ứng phụ không mong muốn và đơn giản hóa mã của bạn.

Cảm ơn bạn đã đọc.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào