Như chúng ta đã biết về bộ dọn rác, công cụ JavaScript lưu trữ một giá trị trong bộ nhớ trong khi nó có thể truy cập được (và có khả năng có thể được sử dụng).

Ví dụ:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

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 memory

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

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

Như thế này:

let john = { name: "John" };

let array = [ john ];

john = null; // overwrite the reference

// john is stored inside the array, so it won't be garbage-collected
// we can get it as array[0]

Tương tự như vậy, nếu chúng ta sử dụng một đối tượng có khóa thông thường là Map, thì trong khi Maptồ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.

Ví dụ:

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()

WeakMapvề cơ bản là khác nhau trong 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.

Hãy xem ý nghĩa của nó trên các ví dụ.

Nội dung chính




WeakMap

Sự khác biệt đầu tiên của WeakMap với Maplà 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 object

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 john = { name: "John" };

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

john = null; // overwrite the reference

// john is removed from memory!

So sánh nó với Map trong ví dụ thông thường ở trên. Bây giờ nếu johnchỉ 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ư johntrong đ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?

Trường hợp dùng: Thêm data

Ứng dụng chính của WeakMaplư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 – đó WeakMapchí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 automatically

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);
}

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;

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 visitsCountMapkhi 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 WeakMapthay 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);
}

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.

Trường hợp sử dụng: 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!)

Đố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.

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

// 📁 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 well

WeakSet

WeakSet 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ợ add, hasdelete, nhưng không size, keys()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 WeakSetcó 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 để WeakSettheo 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 automatically

Hạn chế đáng chú ý nhất của WeakMapWeakSetlà 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.

Tóm lược

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.




WeakMapWeakSetđượ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 WeakMaphoặc trong một WeakSet, nó sẽ tự động được dọn sạch.