Bàn về tính bất biến trong JavaScript

Bàn về tính bất biến trong JavaScript

Những mẩu tin ngắn hàng ngày dành cho bạn
  • Đây! Một vấn đề mà từ xưa đến nay mình cứ thắc mắc mãi, và cho đến hôm qua thì mọi thứ đã sáng tỏ.

    Bình thường mọi người dùng height: 100vh để đặt chiều cao bằng với viewport của màn hình. Trên máy tính thì không vấn đề gì, thậm chí giả lập kích thước của điện thoại thông minh thì mọi thứ vẩn ổn. Nhưng khi mở trên điện thoại thì height 100vh lúc nào cũng vượt quá viewport. Ủa!? Là sao???

    Lý giải cho điều này là do trên thiết bị di động có cách tính viewport khác với máy tính. Nó thường bị can thiệp hay ảnh hưởng bởi thanh địa chỉ, thanh điều hướng của nền tảng mà bạn đang sử dụng. Vậy nên nếu muốn 100vh trên di động đúng bằng viewport thì cần phải làm thêm một bước thiết lập lại viewport.

    Dễ lắm, đầu tiên cần tạo một css variable --vh ở ngay thẻ script đầu trang.

    function updateViewportHeight() { const viewportHeight = globalThis.innerHeight; document.documentElement.style.setProperty('--vh', `${viewportHeight * 0.01}px`); } updateViewportHeight(); window.addEventListener('resize', updateViewportHeight);

    Sau đó thay vì dùng height: 100vh thì chuyển thành height: calc(var(--vh, 1vh) * 100). Thế là xong.

    » Xem thêm
  • Cả ngày hôm nay mình dành thời gian để làm giao diện tiếp thị cho gói hội viên của 2coffee.dev. Vậy là cuối cùng thì cũng chính thức đi vào vào con đường mà 5 năm trước cũng không ngờ đến được: "Bán một cái gì đó". Người ta thường nói "Cho đi để nhận lại", bên cạnh đó cũng có câu "Nếu giỏi một cái gì đó, đừng làm nó miễn phí". Nếu theo dõi đủ lâu, bạn đọc sẽ thấy chẳng có gì mình giấu giếm. Biết gì viết nấy, và đôi khi nhờ viết ra mà nhận lại được sự góp ý của độc giả. Từ đó giúp mình hoàn thiện bản thân nhiều hơn.

    Membership là tính năng mà mình sắp sửa giới thiệu. Trở thành hội viên của blog, bạn sẽ có một số đặc quyền nhất định, ví dụ như truy cập vào các bài viết chỉ dành riêng cho hội viên. Các bài viết này về các chủ đề chuyên sâu và được hệ thống hoá sao cho dễ đọc và dễ nắm bắt nhất. Qua đó cung cấp thêm nhiều kiến thức và trau dồi kỹ năng cho bạn đọc.

    Để đạt được đến ngày hôm nay là công rất lớn của các bạn đọc giả, của những người yêu mến 2coffee.dev. Nhờ các bạn mà blog mới có ngày hôm nay. Bên cạnh đó, bản thân mình cũng phải thay đổi liên tục, phải vượt ra khỏi vùng an toàn, làm những điều mà trước nay không dám. Dù sao đi nữa thì đây cũng mới là khởi đầu cho mọi sự gian nan. Nhưng đừng bao giờ nản nha các bạn ơi 😄

    » Xem thêm
  • Ngày nay, 1 triệu (1M) tác vụ đồng thời sẽ tiêu tốn bao nhiêu bộ nhớ? Đó là câu hỏi của hez2010 và anh đã quyết định đi tìm câu trả lời, bằng cách thử nghiệm một chương trình đơn giản trên nhiều ngôn ngữ lập trình khác nhau: How Much Memory Do You Need in 2024 to Run 1 Million Concurrent Tasks?

    Tóm tắt lại thì Rust vẫn vô đối, nhưng vị trí thứ 2 mới làm tôi cảm thấy ngạc nhiên 😳

    » Xem thêm

Vấn đề

Biến là một thành phần không thể thiếu trong hầu hết ngôn ngữ lập trình. Khi nhắc đến biến, chúng ta thường liên tưởng ngay đến một cú pháp bao gồm từ khóa (keyword), tên biến, kiểu dữ liệu bao gồm cả giá trị ban đầu của nó. Ví dụ một biến trong JavaScript được khai báo như sau:

let name = "2coffee.dev";

Biến - đúng như cái tên của nó, giá trị của biến có thể thay đổi thông qua một phép gán. Việc thay đổi giá trị của biến giúp cho người lập trình tái sử dụng lại được tên biến, tiết kiệm bộ nhớ và tăng tính linh hoạt trong lập trình. Tưởng tượng biến name ở trong ví dụ trên, khi đến dòng x, giá trị cũ không còn tác dụng nữa, mà cần phải được thay thế thành "https://2coffee.dev" trước khi trả dữ liệu về cho người dùng chẳng hạn. Nói tóm lại, thay đổi giá trị của một biến là một điều hết sức bình thường.

Nhưng mọi chuyện sẽ dần trở nên rắc rối khi trong mã sử dụng nhiều biến hơn. Việc tạo ra nhiều biến thường sẽ tỉ lệ thuận với độ phức tạp của logic xử lý vấn đề. Tôi từng viết ra một hàm mà trong đó sử dụng đến hàng chục biến khác nhau. Hãy thử tưởng tượng nếu tất cả các biến đó được thay đổi liên tục thì dòng chảy dữ liệu của bạn lúc này sẽ như thế nào?

Đơn giản! chỉ cần tuân theo quy tắc khai báo dữ liệu không thay đổi bằng từ khóa const, khi đó biến sẽ không bao giờ bị gán lại giá trị nữa, const hay còn được biến đến là khai báo hằng số, mà đã là hằng số thì không bị thay đổi. Thông thường, gần như khi tạo ra một biến, theo thói quen tôi sẽ gõ const vì lợi ích mà nó mang lại là: hạn chế việc thay đổi dữ liệu của biến.

Nhưng trong thế giới của JavaScript, không gì là không thể xảy ra. Khi sử dụng const cho các biến chứa giá trị là đối tượng như Object, Array... const ngăn được việc gán giá trị, nhưng lại không thể ngăn được dữ liệu bên trong đối tượng thay đổi.

const website = {
  name: "2coffee.dev",  
};

website.name = "https://2coffee.dev";
console.log(website.name); // https://2coffee.dev

Khi đó, dữ liệu lại tha hồ "nhảy múa" trong luồng xử lý logic của bạn. Nhiều người nghĩ: "có gì nghiêm trọng ở đây đâu, dữ liệu thay đổi cũng là chuyện bình thường mà..." thì tôi chắc chắn rằng họ chưa bao giờ debug và maintain một hàm có hàng chục loại biến. Dữ liệu thay đổi, đồng nghĩa với việc bạn phải quan sát chúng mọi lúc mọi nơi, khi nhìn vào mã mà không thể đoán được tại dòng x, vị trí y, biến data này đang nắm giữ dữ liệu gì, thì khả năng cao sau này bảo trì sẽ vô cùng khó khăn. Nếu bạn là tác giả của đoạn mã đó thì còn có thể an tâm mà sửa, còn nếu như là người khác, họ có thể vô tình tạo ra một số lỗi khác khi đang cố gắng thay đổi "dòng chảy dữ liệu" mà bạn tạo ra trước đó.

Vậy bài học rút ra là gì?

Tính bất biến

Tính bất biến trong lập trình JavaScript đề cập đến khả năng của biến và cấu trúc dữ liệu không thay đổi sau khi chúng đã được tạo. Điều này có nghĩa là giá trị của chúng không thể thay đổi sau khi đã được xác định. Tính bất biến mang lại ổn định và dễ dàng theo dõi trong mã nguồn, giúp ngăn chặn các thay đổi không mong muốn và tạo ra một cơ sở cho kiểm thử và bảo trì dễ dàng.

Trong JavaScript, chúng ta thường sử dụng từ khóa const để tạo tính bất biến trên các kiểu dữ liệu nguyên thủy - Primitive, Object.freeze cho dữ liệu dạng đối tượng.

const website = {
  name: "2coffee.dev",  
};

Object.freeze(website);

website.name = "https://2coffee.dev";
console.log(website.name); // 2coffee.dev

Ở trong ví dụ trên, sau khi sử dụng Object.freeze(website), ngay lập tức đối tượng website bị "đóng băng" và dữ liệu trong các thuộc tính của nó sẽ không thể nào thay đổi được nữa, dù cho chúng ta có cố gắng gán lại bao nhiêu lần. Tuyệt vời, nếu vậy thì chẳng phải const cho nguyên thủy, Object.freeze cho đối tượng là mọi vấn đề về tính bất biến đã được giải quyết rồi sao?

Nhưng rất tiếc, Object.freeze chỉ "đóng băng" nông (shallow) được thôi, nếu trong website có thuộc tính có kiểu dữ liệu là object thì nó hoàn toàn có thể thay đổi.

const website = {
  name: "2coffee.dev",  
  props: {
    color: "black",  
  }
};

Object.freeze(website);

website.props.color = "white";
console.log(website.props.color); // white

Để giải quyết vấn đề này, chúng ta phải tự triển khai một hàm có khả năng "đóng băng" sâu (deep) một đối tượng, tức là phải đóng băng được thuộc tính của thuộc tính của thuộc tính... của đối tượng.

function deepFreeze(object) {
  // Retrieve the property names defined on object
  const propNames = Reflect.ownKeys(object);

  // Freeze properties before freezing self
  for (const name of propNames) {
    const value = object[name];

    if ((value && typeof value === "object") || typeof value === "function") {
      deepFreeze(value);
    }
  }

  return Object.freeze(object);
}

const website = {
  name: "2coffee.dev",  
  props: {
    color: "black",  
  }
};

deepFreeze(website);

website.props.color = "white";
console.log(website.props.color); // black

Ưu và nhược điểm của tính bất biến

Khi dữ liệu không bị thay đổi, chúng ta có thể quan sát được dữ liệu ở mọi lúc mọi nơi, sử dụng lại chúng mà không lo lắng về việc bị thay đổi ở một nơi nào đó. Trước kia, tôi thường cố gắng thay đổi dữ liệu bên trong một mảng hoặc một đối tượng, sau đó đối tượng lại được dùng ở một nơi khác và gây ra vấn đề sai lệch dữ liệu, dẫn đến logic xử lý bị hỏng hoàn toàn.

Giảm thiểu lỗi trong quá trình phát triển, một trong số đó có thể kể đến như Đôi điều về Object Reference trong JavaScript. Nhiều lúc quên thật phiền toái!. Khi vô tình thay đổi dữ liệu tham chiếu, kéo theo là bị thay đổi toàn bộ ở những nơi sử dụng dữ liệu đó.

Dễ dàng trong quá trình kiểm thử, tăng cường bảo mật do dữ liệu không được thay đổi từ một nơi nào đó trong mã.

Tuy vậy, tính bất biến cũng mang lại nhiều vấn đề, nổi bật nhất chính là chi phí cho bộ nhớ, vì cứ mỗi lần thay đổi dữ liệu thì cần tạo ra một đối tượng "ôm" hết dữ liệu mới. Bộ nhớ tăng, kéo theo là cả hiệu năng bị ảnh hưởng, tăng sự phức tạp, nhiều mã cần phải viết ra hơn cho một chức năng đơn giản.

Lời khuyên

JavaScript là một ngôn ngữ linh hoạt, không bị ràng buộc bởi các kiểu dữ liệu và rất "phóng khoáng" trong cách viết. Sẽ có những dự án, bạn thấy họ áp dụng tính bất biến một cách nghiêm ngặt và ngược lại, tính bất biến lại chỉ nên xuất hiện ở nơi cần thiết.

Theo kinh nghiệm cá nhân, tôi luôn sử dụng const trong khai báo tất cả các biến, trong trường hợp biến đó cần được gán lại thì xem xét sang một cách viết khác để hạn chế việc thay đổi dữ liệu của biến.

Khi xử lý dữ liệu là Array hoặc Object, hạn chế việc thay đổi trực tiếp dữ liệu bên trong nó, nếu dữ liệu cần thay đổi quá nhiều so với hiện tại, thì tốt nhất là tạo ra một biến với bộ dữ liệu mới. Hãy cố gắng sao chép sâu (deep copy) để tránh các vấn đề "object reference" sau này, clone | npm có thể là một thư viện hữu ích cho bạn.

Nếu cần nghiêm ngặt hơn, hãy xem xét đến việc sử dụng thêm Object.freeze hoặc các thư viện hỗ trợ tạo các đối tượng bất biến như là Immutable.js. Khi đó mọi người sẽ làm việc trên cùng một bộ quy tắc chung, tránh "đột biến" dữ liệu ngoài tầm kiểm soát.

Cao cấp
Hello

Bí mật ngăn xếp của Blog

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Là một lập trình viên, bạn có tò mò về bí mật công nghệ hay những khoản nợ kỹ thuật về trang blog này? Tất cả bí mật sẽ được bật mí ngay bài viết dưới đây. Còn chờ đợi gì nữa, hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

Bình luận (0)

Nội dung bình luận...