Quản lý bộ nhớ và hiệu suất là những khía cạnh quan trọng của phát triển phần mềm và là những khía cạnh mà mọi nhà phát triển phần mềm nên chú ý. Mặc dù hữu ích, nhưng tham chiếu yếu không thường được sử dụng trong JavaScript.
Trước khi tìm hiểu về tham chiếu mạnh,
Đầu ra của mã trên sẽ như sau:
Đối số
Có vẻ như chúng ta đang tiến triển. Hãy nói về tham chiếu mạnh, sau đó chúng ta sẽ liên kết mọi thứ lại với nhau.
Các đoạn mã sau minh họa khái niệm tham chiếu mạnh:
Kết quả của đoạn mã trên sẽ như sau:
Không thể truy cập đối tượng thông qua biến
Điểm quan trọng cần lưu ý ở đây là tham chiếu yếu không ngăn đối tượng khỏi bị thu gom rác, trong khi tham chiếu mạnh ngăn đối tượng khỏi bị thu gom rác.
Các giá trị được coi là có thể đạt được nếu chúng là:
Ở đây chúng ta có một đối tượng có cặp khóa-giá trị (có tên
… thì đối tượng sẽ được thu gom rác và giá trị
Từ các đoạn mã trên, chúng ta có thể truy cập thuộc tính đối tượng từ cả biến
… thì đối tượng vẫn sẽ nằm trong bộ nhớ vì có thể truy cập thông qua biến
Lưu ý: Theo mặc định, JavaScript sử dụng tham chiếu mạnh cho các tham chiếu của nó. Để triển khai tham chiếu yếu trong JavaScript, bạn sẽ sử dụng
Hãy minh họa điều này bằng các đoạn mã sau:
Chúng ta cũng có thể sử dụng trình lặp
Ở dòng 1, chúng ta đã tạo một thể hiện của
Các thuộc tính của một cấu trúc dữ liệu được coi là có thể truy cập được khi cấu trúc dữ liệu đó nằm trong bộ nhớ và chúng thường được lưu trong bộ nhớ. Nếu chúng ta lưu trữ một đối tượng trong một mảng, thì miễn là mảng đó còn trong bộ nhớ, đối tượng đó vẫn có thể được truy cập ngay cả khi nó không có tham chiếu nào khác.
Chúng ta vẫn có thể truy cập đối tượng này ngay cả khi tham chiếu đã bị ghi đè vì đối tượng đã được lưu trong mảng; do đó, đối tượng đã được lưu trong bộ nhớ miễn là mảng vẫn còn trong bộ nhớ. Do đó, đối tượng không bị thu gom rác. Vì chúng ta đã sử dụng một mảng trong ví dụ trên, chúng ta cũng có thể sử dụng
Giống như một đối tượng,
Theo Mozilla Developer Network, đối tượng
Không giống như
Với
Các đoạn trích dưới đây minh họa cách
Một tác dụng phụ chính của việc sử dụng các đối tượng làm khóa trong
Chúng ta hãy xem điều này hoạt động như thế nào. Tạo một tệp, đặt tên là
Nếu chúng ta sử dụng
Chúng ta hãy tạo mã máy khách cho việc này:
Với
WeakSet
và WeakMap
đã được giới thiệu trong JavaScript trong phiên bản ES6.Tham chiếu yếu
Để làm rõ, không giống như tham chiếu mạnh, tham chiếu yếu không ngăn đối tượng được tham chiếu bị thu hồi hoặc thu thập bởi trình thu gom rác, ngay cả khi đó là tham chiếu duy nhất đến đối tượng trong bộ nhớ.Trước khi tìm hiểu về tham chiếu mạnh,
WeakSet
, Set
, WeakMap
và Map
, hãy minh họa tham chiếu yếu bằng đoạn mã sau:
Mã:
// Tạo một phiên bản của đối tượng WeakMap.let human = new WeakMap():// Tạo một đối tượng và gán nó cho một biến có tên là man.let man = { name: "Joe Doe" };// Gọi phương thức set trên human và truyền hai đối số (khóa và giá trị) cho nó.human.set(man, "done")console.log(human)
Mã:
WeakMap {{…} => 'done'}man = null;console.log(human)
man
hiện được đặt thành đối tượng WeakMap
. Tại thời điểm chúng ta gán lại biến man
thành null
, tham chiếu duy nhất đến đối tượng gốc trong bộ nhớ là tham chiếu weak và nó đến từ WeakMap
mà chúng ta đã tạo trước đó. Khi công cụ JavaScript chạy quy trình thu gom rác, đối tượng man
sẽ bị xóa khỏi bộ nhớ và khỏi WeakMap
mà chúng ta đã gán cho nó. Lý do là vì nó là tham chiếu yếu và không ngăn chặn việc thu gom rác.Có vẻ như chúng ta đang tiến triển. Hãy nói về tham chiếu mạnh, sau đó chúng ta sẽ liên kết mọi thứ lại với nhau.
Tham chiếu mạnh
Tham chiếu mạnh trong JavaScript là tham chiếu ngăn chặn đối tượng bị thu gom rác. Nó giữ đối tượng trong bộ nhớ.Các đoạn mã sau minh họa khái niệm tham chiếu mạnh:
Mã:
let man = {name: "Joe Doe"};let human = [man];man = null;console.log(human);
Mã:
// Một mảng các đối tượng có độ dài 1.[{…}]
man
nữa do có tham chiếu mạnh giữa mảng human
và đối tượng. Đối tượng được giữ lại trong bộ nhớ và có thể truy cập bằng đoạn mã sau:
Mã:
console.log(human[0])
Thu gom rác trong JavaScript
Giống như mọi ngôn ngữ lập trình, quản lý bộ nhớ là yếu tố chính cần cân nhắc khi viết JavaScript. Không giống như C, JavaScript là ngôn ngữ lập trình cấp cao tự động phân bổ bộ nhớ khi các đối tượng được tạo và tự động xóa bộ nhớ khi các đối tượng không còn cần thiết nữa. Quá trình xóa bộ nhớ khi các đối tượng không còn được sử dụng được gọi là thu gom rác. Hầu như không thể nói về thu gom rác trong JavaScript mà không đề cập đến khái niệm khả năng tiếp cận.Khả năng tiếp cận
Tất cả các giá trị nằm trong một phạm vi cụ thể hoặc đang được sử dụng trong một phạm vi được cho là "có thể tiếp cận" trong phạm vi đó và được gọi là "giá trị có thể tiếp cận". Các giá trị có thể đạt được luôn được lưu trữ trong bộ nhớ.Các giá trị được coi là có thể đạt được nếu chúng là:
- các giá trị trong gốc của chương trình hoặc được tham chiếu từ gốc, chẳng hạn như các biến toàn cục hoặc hàm hiện đang thực thi, ngữ cảnh và hàm gọi lại của nó;
- các giá trị có thể truy cập từ gốc bằng một tham chiếu hoặc chuỗi tham chiếu (ví dụ: một đối tượng trong biến toàn cục tham chiếu đến một đối tượng khác, đối tượng này cũng tham chiếu đến một đối tượng khác — tất cả đều được coi là các giá trị có thể đạt được).
Mã:
let languages = {name: “JavaScript”};
JavaScript
) tham chiếu đến biến toàn cục languages
. Nếu chúng ta ghi đè giá trị của languages
bằng cách gán null
cho nó…
Mã:
languages = null;
JavaScript
không thể truy cập lại được nữa. Sau đây là một ví dụ khác:
Mã:
let languages = {name: “JavaScript”};let programmer = languages;
languages
và biến programmer
. Tuy nhiên, nếu chúng ta đặt languages
thành null
…
Mã:
languages = null;
programmer
. Đây là cách hoạt động của bộ thu gom rác tóm lại.Lưu ý: Theo mặc định, JavaScript sử dụng tham chiếu mạnh cho các tham chiếu của nó. Để triển khai tham chiếu yếu trong JavaScript, bạn sẽ sử dụng
WeakMap
, WeakSet
hoặc WeakRef
.So sánh Set và WeakSet
Đối tượng set là tập hợp các giá trị duy nhất có một lần xuất hiện duy nhất. Một set, giống như một mảng, không có cặp khóa-giá trị. Chúng ta có thể lặp qua một tập hợp các mảng bằng các phương thức mảngfor… of
và .forEach
.Hãy minh họa điều này bằng các đoạn mã sau:
Mã:
let setArray = new Set(["Joseph", "Frank", "John", "Davies"]);for (let names of setArray){ console.log(names)}// Joseph Frank John Davies
.forEach
:
Mã:
setArray.forEach((name, nameAgain, setArray) =>{ console.log(names); });
WeakSet
là một tập hợp các đối tượng duy nhất. Như tên gọi, WeakSet
sử dụng tham chiếu yếu. Sau đây là các thuộc tính của WeakSet()
:- Nó chỉ có thể chứa các đối tượng.
- Các đối tượng trong tập hợp có thể được truy cập ở nơi khác.
- Nó không thể được lặp qua.
- Giống như
Set()
,WeakSet()
có các phương thứcadd
,has
vàdelete
.
WeakSet()
và một số phương thức có sẵn:
Mã:
const human = new WeakSet();let paul = {name: "Paul"};let mary = {gender: "Mary"};// Thêm người có tên là paul vào lớp học.const classroom = human.add(paul);console.log(classroom.has(paul)); // truepaul = null;// Lớp học sẽ tự động được dọn sạch khỏi con người paul.console.log(classroom.has(paul)); // false
WeakSet()
. Ở dòng 3 và 4, chúng ta đã tạo các đối tượng và gán chúng cho các biến tương ứng của chúng. Ở dòng 7, chúng ta đã thêm paul
vào WeakSet()
và gán nó cho biến classroom
. Ở dòng 11, chúng ta đã tạo tham chiếu paul
là null
. Mã ở dòng 15 trả về false
vì WeakSet()
sẽ tự động được dọn sạch; vì vậy, WeakSet()
không ngăn chặn việc thu gom rác.So sánh Map và WeakMap
Như chúng ta đã biết từ phần về việc thu gom rác ở trên, công cụ JavaScript giữ một giá trị trong bộ nhớ miễn là có thể truy cập được. Hãy minh họa điều này bằng một số đoạn mã:
Mã:
let smashing = {name: "magazine"};// Có thể truy cập đối tượng từ tham chiếu.// Ghi đè tham chiếu smashing.smashing = null;// Không thể truy cập đối tượng nữa.
Mã:
let smashing = {name: "magazine"};let arr = [smashing];// Ghi đè tham chiếu.smashing = null;console.log(array[0]) // {name: 'magazine'}
map
. Trong khi map
vẫn tồn tại, các giá trị được lưu trữ trong đó sẽ không bị thu gom rác.
Mã:
let map = new Map();let smashing {name: "magazine"};map.set(smashing, "blog");// Ghi đè tham chiếu.smashing = null;// Để truy cập đối tượng.console.log(map.keys());
map
có thể chứa các cặp khóa-giá trị và chúng ta có thể truy cập giá trị thông qua khóa. Nhưng với map
, chúng ta phải sử dụng phương thức .get()
để truy cập các giá trị.Theo Mozilla Developer Network, đối tượng
Map
chứa các cặp khóa-giá trị và ghi nhớ thứ tự chèn ban đầu của các khóa. Bất kỳ giá trị nào (cả đối tượng và giá trị nguyên thủy) đều có thể được sử dụng làm khóa hoặc giá trị.Không giống như
map
, WeakMap
chứa một tham chiếu yếu; do đó, nó không ngăn chặn việc thu gom rác xóa các giá trị mà nó tham chiếu nếu các giá trị đó không được tham chiếu mạnh ở nơi khác. Ngoài ra, WeakMap
giống với map
. WeakMap
không thể liệt kê được do tham chiếu yếu.Với
WeakMap
, các khóa phải là đối tượng và các giá trị có thể là số hoặc chuỗi.Các đoạn trích dưới đây minh họa cách
WeakMap
hoạt động và các phương thức trong đó:
Mã:
// Tạo một weakMap.let weakMap = new WeakMap();let weakMap2 = new WeakMap();// Tạo một đối tượng.let ob = {};// Sử dụng phương thức set.weakMap.set(ob, "Done");// Bạn có thể đặt giá trị thành một đối tượng hoặc thậm chí là một hàm.weakMap.set(ob, ob)// Bạn có thể đặt giá trị thành undefined.weakMap.set(ob, undefined);// WeakMap cũng có thể là giá trị và khóa.weakMap.set(weakMap2, weakMap)// Để lấy giá trị, hãy sử dụng phương thức get.weakMap.get(ob) // Xong// Sử dụng phương thức has.weakMap.has(ob) // trueweakMap.delete(ob)weakMap.has(ob) // false
WeakMap
mà không có tham chiếu nào khác đến nó là chúng sẽ tự động bị xóa khỏi bộ nhớ trong quá trình thu gom rác.Các lĩnh vực ứng dụng của WeakMap
WeakMap
có thể được sử dụng trong hai lĩnh vực phát triển web: lưu trữ đệm và lưu trữ dữ liệu bổ sung.Lưu trữ đệm
Đây là kỹ thuật web liên quan đến việc lưu (tức là lưu trữ) một bản sao của một tài nguyên nhất định và phục vụ lại khi được yêu cầu. Kết quả từ một hàm có thể được lưu vào bộ nhớ đệm để bất cứ khi nào hàm được gọi, kết quả được lưu vào bộ nhớ đệm có thể được sử dụng lại.Chúng ta hãy xem điều này hoạt động như thế nào. Tạo một tệp, đặt tên là
cachedResult.js
và viết nội dung sau vào đó:
Mã:
let cachedResult = new WeakMap(); // Một hàm lưu trữ kết quả.function keep(obj){if(!cachedResult.has(obj){ let result = obj; cachedResult.set(obj, result); }return cachedResult.get(obj);}let obj = {name: "Frank"};let resultSaved = keep(obj)obj = null;// console.log(cachedResult.size); Có thể dùng map, không dùng WeakMap
Map()
thay vì WeakMap()
trong mã trên và có nhiều lần gọi hàm keep()
, thì hàm sẽ chỉ tính toán kết quả vào lần đầu tiên hàm được gọi và sẽ lấy kết quả từ cachedResult
vào những lần khác. Tác dụng phụ là chúng ta sẽ cần phải dọn dẹp cachedResult
bất cứ khi nào không cần đến đối tượng. Với WeakMap()
, kết quả được lưu trong bộ nhớ đệm sẽ tự động xóa khỏi bộ nhớ ngay khi đối tượng được thu gom rác. Lưu trữ đệm là một phương tiện tuyệt vời để cải thiện hiệu suất phần mềm — nó có thể tiết kiệm chi phí sử dụng cơ sở dữ liệu, các lệnh gọi API của bên thứ ba và các yêu cầu từ máy chủ đến máy chủ. Với lưu trữ đệm, một bản sao kết quả từ một yêu cầu được lưu cục bộ.Dữ liệu bổ sung
Một công dụng quan trọng khác củaWeakMap()
là lưu trữ dữ liệu bổ sung. Hãy tưởng tượng chúng ta đang xây dựng một nền tảng thương mại điện tử và chúng ta có một chương trình đếm số lượng khách truy cập và chúng ta muốn có thể giảm số lượng khi khách truy cập rời đi. Nhiệm vụ này sẽ rất khó khăn với Map, nhưng khá dễ triển khai với WeakMap()
:
Mã:
let visitorCount = new WeakMap();function countCustomer(customer){ let count = visitorCount.get(customer) || 0; visitorCount.set(customer, count + 1);}
Mã:
let person = {name: "Frank"};// Đếm số lượt truy cập của người.countCustomer(person)// Người rời đi.person = null;
Map()
, chúng ta sẽ phải dọn sạch visitorCount
bất cứ khi nào khách hàng rời đi; nếu không, nó sẽ tăng lên vô thời hạn trong bộ nhớ, chiếm dung lượng. Nhưng với WeakMap()
, chúng ta không cần phải dọn sạch visitorCount
; ngay khi một người (đối tượng) không thể truy cập được, nó sẽ tự động được thu gom rác.Kết luận
Trong bài viết này, chúng ta đã tìm hiểu về tham chiếu yếu, tham chiếu mạnh và khái niệm khả năng tiếp cận, đồng thời chúng tôi đã cố gắng kết nối chúng với quản lý bộ nhớ tốt nhất có thể. Tôi hy vọng bạn thấy bài viết này hữu ích. Hãy thoải mái để lại bình luận.Đọc thêm
- Xây dựng trình đọc RSS tĩnh để chống lại FOMO bên trong bạn
- Cách khắc phục sự cố sơn nội dung lớn nhất bằng Phân tích phần phụ
- Tích hợp: Từ truyền dữ liệu đơn giản đến kiến trúc có thể cấu hình hiện đại
- Sự cường điệu xung quanh tín hiệu