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 david = { name: "David" };
// the object can be accessed, david is the reference to it
// overwrite the reference
david = 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 david = { name: "David" };
let array = [ david ];
david = null; // overwrite the reference
// david 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 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.
Ví dụ:
let david = { name: "John" };
let map = new Map();
map.set(david, "...");
david = null; // overwrite the reference
// david is stored inside the map,
// we can get it by using map.keys()
WeakMap
về 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
1. WeakMap
Sự khác biệt đầu tiên của WeakMap
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 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 david = { name: "David" };
let weakMap = new WeakMap();
weakMap.set(david, "...");
david = null; // overwrite the reference
// david 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 david
chỉ 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ớ).
WeakMap
khô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ư david
trong đ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 WeakMap
khô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?
2. Trường hợp dùng: 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 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 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ùngWeakMap
thay 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
.
3. 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:
/*
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
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
*/
// 📁 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 cache
khi không cần đối tượng nữa.
Nếu chúng ta thay thế Map
bằ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
4. 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àoWeakSet
(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
,has
vàdelete
, nhưng khôngsize
,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 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 automatically
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.
5. 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.
WeakSet
là 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.
Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.
Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:
Chào thân ái và quyết thắng!