WeakMap và WeakSet trong JavaScript

WeakMap và WeakSet trong JavaScript

Trong bài viết này, chúng ta sẽ tìm hiểu về WeakMap và WeakSet trong JavaScript. Có thể bạn chưa biết, JavaScript engine giữ một giá trị trong bộ nhớ trong khi nó “có thể truy cập được” và được sử dụng.

Ví dụ:

let john = { name: "John" };

// the object can be accessed, john is the reference to it

// overwrite the reference
john = null;

// the object will be removed from memoryCode language: JavaScript (javascript)

Thông thường, các thuộc tính của một đối tượng, các phần tử của mảng hoặc cấu trúc dữ liệu khác được coi là có thể truy cập được và được lưu giữ trong bộ nhớ khi cấu trúc dữ liệu đó ở trong bộ nhớ.

Ví dụ, nếu chúng ta đặt một đối tượng vào một mảng, trong khi mảng còn tồn tại, thì đối tượng đó cũng sẽ tồn tại, ngay cả khi không có tham chiếu nào khác đến nó.

let john = { name: "John" };

let array = [ john ];

john = null; // overwrite the reference

// the object previously referenced by john is stored inside the array
// therefore it won't be garbage-collected
// we can get it as array[0]Code language: JavaScript (javascript)

Tương tự như vậy, nếu chúng ta sử dụng một đối tượng làm khóa trong Map, thì khi Map tồn tại, đối tượng đó cũng tồn tại. Nó chiếm bộ nhớ và có thể không được thu gom rác.

let john = { name: "John" };

let map = new Map();
map.set(john, "...");

john = null; // overwrite the reference

// john is stored inside the map,
// we can get it by using map.keys()Code language: JavaScript (javascript)

WeakMap về cơ bản khác biệt ở khía cạnh này. Nó không ngăn chặn việc thu gom rác của các đối tượng chính.

WeakMap trong JavaScript

WeakMap và WeakSet trong JavaScript

Sự khác biệt đầu tiên của WeakMap trong JavaScript với Map là nó có các khóa phải là đối tượng, không phải giá trị nguyên thủy:

let weakMap = new WeakMap();

let obj = {};

weakMap.set(obj, "ok"); // works fine (object key)

// can't use a string as the key
weakMap.set("test", "Whoops"); // Error, because "test" is not an objectCode language: JavaScript (javascript)

Bây giờ, nếu chúng ta sử dụng một đối tượng làm khóa trong đó và không có tham chiếu nào khác đến đối tượng đó – nó sẽ tự động bị xóa khỏi bộ nhớ (và khỏi map).

let david = { name: "David" };

let weakMap = new WeakMap();
weakMap.set(david, "...");

david = null; // overwrite the reference

// david is removed from memory!Code language: JavaScript (javascript)

So sánh nó với Map trong ví dụ thông thường ở trên. Bây giờ nếu davidchỉ tồn tại dưới dạng khóa của WeakMap– nó sẽ tự động bị xóa khỏi map (và bộ nhớ).

WeakMapkhông hỗ trợ lặp và phương thức keys()values()entries(), vì vậy không có cách nào để có được tất cả các key hoặc giá trị từ nó.

WeakMap chỉ có các phương thức sau:

  • weakMap.get(key)
  • weakMap.set(key, value)
  • weakMap.delete(key)
  • weakMap.has(key)

Tại sao lại hạn chế như vậy? Đó là vì lý do kỹ thuật. Nếu một đối tượng đã mất tất cả các tham chiếu khác (như davidtrong đoạn code trên), thì nó sẽ được thu gom rác tự động. Nhưng về mặt kỹ thuật, nó không được chỉ định chính xác khi việc dọn dẹp xảy ra .

Công cụ JavaScript quyết định điều đó. Nó có thể chọn thực hiện việc dọn dẹp bộ nhớ ngay lập tức hoặc chờ và thực hiện việc dọn dẹp sau khi có nhiều thao tác xóa. Vì vậy, về mặt kỹ thuật, số lượng phần tử hiện tại của một WeakMapkhông được biết đến. Công cụ có thể đã làm sạch nó hoặc không, hoặc đã làm một phần. Vì lý do đó, các phương thức truy cập tất cả các khóa / giá trị không được hỗ trợ.

Bây giờ chúng ta cần dùng cấu trúc dữ liệu như vậy ở đâu?

Thêm data

Ứng dụng chính của WeakMap là lưu trữ dữ liệu bổ sung.

Nếu chúng ta đang làm việc với một đối tượng mà thuộc về một code khác, thậm chí là thư viện của bên thứ ba và muốn lưu trữ một số dữ liệu liên quan đến nó, thì nó chỉ tồn tại khi đối tượng còn sống – WeakMap chính xác là những gì cần thiết .

Chúng ta đặt dữ liệu vào một WeakMap, sử dụng đối tượng làm khóa và khi đối tượng được thu gom rác, dữ liệu đó cũng sẽ tự động biến mất.

weakMap.set(john, "secret documents");
// if john dies, secret documents will be destroyed automaticallyCode language: JavaScript (javascript)

Hãy xem xét một ví dụ.

Chẳng hạn, chúng ta có code giữ số lượt truy cập của người dùng. Thông tin được lưu trữ trong map: đối tượng người dùng là chìa khóa và số lượt truy cập là giá trị. Khi người dùng rời khỏi (đối tượng của nó bị thu gom rác), chúng ta không muốn lưu trữ số lượt truy cập của họ nữa.

Đây là một ví dụ về hàm đếm với Map:

// 📁 visitsCount.js
let visitsCountMap = new Map(); // map: user => visits count

// increase the visits count
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}Code language: JavaScript (javascript)

Và đây là một phần khác của code, có thể là một tệp khác sử dụng nó:

// 📁 main.js
let john = { name: "John" };

countUser(john); // count his visits

// later john leaves us
john = null;Code language: JavaScript (javascript)

Bây giờ đối tượng john nên được thu gom rác, nhưng vẫn còn trong bộ nhớ, vì đó là một khóa trong visitsCountMap.

Chúng ta cần dọn dẹp visitsCountMap khi xóa người dùng, nếu không nó sẽ tăng lên trong bộ nhớ vô thời hạn. Làm sạch như vậy có thể trở thành một nhiệm vụ tẻ nhạt trong các kiến ​​trúc phức tạp.

Chúng ta có thể tránh nó bằng cách chuyển sang dùngWeakMapthay thế:

// 📁 visitsCount.js
let visitsCountMap = new WeakMap(); // weakmap: user => visits count

// increase the visits count
function countUser(user) {
  let count = visitsCountMap.get(user) || 0;
  visitsCountMap.set(user, count + 1);
}Code language: JavaScript (javascript)

Bây giờ chúng ta không phải làm sạch visitsCountMap. Sau khi mọi đối tượng john trở nên không thể truy cập bằng mọi cách ngoại trừ khóa WeakMap, nó sẽ bị xóa khỏi bộ nhớ, cùng với thông tin của khóa đó từ WeakMap.

Bộ nhớ đệm

Một ví dụ phổ biến khác là bộ nhớ đệm: khi một kết quả của hàm nên được ghi nhớ (bộ nhớ cache), để các lệnh gọi trong tương lai trên cùng một đối tượng sử dụng lại nó.

Chúng ta có thể sử dụng Mapđể lưu trữ kết quả, như thế này:

// 📁 cache.js
let cache = new Map();

// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculations of the result for */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

// Now we use process() in another file:

// 📁 main.js
let obj = {/* let's say we have an object */};

let result1 = process(obj); // calculated

// ...later, from another place of the code...
let result2 = process(obj); // remembered result taken from cache

// ...later, when the object is not needed any more:
obj = null;

alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!)Code language: JavaScript (javascript)

Đối với nhiều cuộc gọi của process(obj) cùng một đối tượng, nó chỉ tính kết quả lần đầu tiên và sau đó chỉ thực hiện từ cache. Nhược điểm là chúng ta cần làm sạch cachekhi không cần đối tượng nữa.

Nếu chúng ta thay thế Mapbằng WeakMap, thì vấn đề này sẽ biến mất: kết quả được lưu trong bộ nhớ cache sẽ tự động bị xóa khỏi bộ nhớ sau khi đối tượng được thu gom rác.

// 📁 cache.js
let cache = new WeakMap();

// calculate and remember the result
function process(obj) {
  if (!cache.has(obj)) {
    let result = /* calculate the result for */ obj;

    cache.set(obj, result);
  }

  return cache.get(obj);
}

// 📁 main.js
let obj = {/* some object */};

let result1 = process(obj);
let result2 = process(obj);

// ...later, when the object is not needed any more:
obj = null;

// Can't get cache.size, as it's a WeakMap,
// but it's 0 or soon be 0
// When obj gets garbage collected, cached data will be removed as wellCode language: JavaScript (javascript)

WeakSet trong JavaScript

WeakMap và WeakSet trong JavaScript

WeakSet trong JavaScript hành xử tương tự:

  • Nó tương tự như Set, nhưng chúng ta chỉ có thể thêm các đối tượng vào WeakSet (không phải là kiểu nguyên thủy).
  • Một đối tượng tồn tại trong tập hợp trong khi nó có thể truy cập từ một nơi khác.
  • Giống như Set, nó hỗ trợ addhasvà delete, nhưng không sizekeys()và không lặp lại.

Là liên kết yếu kém, nó cũng phục vụ để lưu trữ dữ liệu. Nhưng không phải cho một dữ liệu tùy ý. Một thành viên trong WeakSet có thể là một cái gì đó về đối tượng.

Chẳng hạn, chúng ta có thể thêm người dùng để WeakSet theo dõi những người đã truy cập trang web của chúng ta:

let visitedSet = new WeakSet();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

visitedSet.add(john); // John visited us
visitedSet.add(pete); // Then Pete
visitedSet.add(john); // John again

// visitedSet has 2 users now

// check if John visited?
alert(visitedSet.has(john)); // true

// check if Mary visited?
alert(visitedSet.has(mary)); // false

john = null;

// visitedSet will be cleaned automaticallyCode language: JavaScript (javascript)

Hạn chế đáng chú ý nhất của WeakMap và WeakSet là vắng mặt của việc lặp lại và không có khả năng để có được tất cả các phần tử hiện tại. Điều đó có vẻ bất tiện, nhưng không ngăn cản WeakMap/WeakSet thực hiện công việc chính của nó – là một kho lưu trữ dữ liệu cho các đối tượng được lưu trữ / quản lý ở nơi khác.

Kết luận

WeakMap và WeakSet trong JavaScript

WeakMap là một sưu tập giống như Map nhưng chỉ cho phép các đối tượng làm khóa và loại bỏ chúng cùng với giá trị được liên kết một khi chúng không thể truy cập được bằng các phương tiện khác.

WeakSetlà bộ sưu tập giống như set nhưng chỉ lưu trữ các đối tượng và loại bỏ chúng một khi chúng không thể truy cập bằng các phương tiện khác.

Cả hai đều không hỗ trợ các phương thức và thuộc tính tham chiếu đến tất cả các khóa hoặc đếm số lượng của chúng.

WeakMap và WeakSet được sử dụng làm cấu trúc dữ liệu, ngoài việc lưu trữ đối tượng thì khi đối tượng được xóa khỏi bộ lưu trữ chính, nếu nó chỉ được tìm thấy dưới dạng khóa của WeakMap hoặc trong một WeakSet, nó sẽ tự động được dọn sạch.

Cảm ơn các bạn đã theo dõi bài viết!

Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

TỔNG HỢP TÀI LIỆU HỌC LẬP TRÌNH CƠ BẢN CHO NGƯỜI MỚI BẮT ĐẦU

KHOÁ HỌC BOOTCAMP JAVA/PHP/.NET TRỞ THÀNH LẬP TRÌNH VIÊN TRONG 5-6 THÁNG

Bình luận