ทำความเข้าใจว่า garbage collector ทำงานอย่างไร ใช้ weak reference เพื่อหลีกเลี่ยงการรั่วไหล และปรับงานที่มีต้นทุนสูงให้ราบรื่นด้วย debounce และ throttle
เปิดบทเรียนนี้ใน Kodokongarbage collector ของ V8 อิงกับ reachability อ็อบเจกต์จะเก็บกวาดได้ทันทีที่ไม่มีสายโซ่ของการอ้างอิงเชื่อมมันกับ root (สแตก, ขอบเขตที่กำลังทำงาน, global) GC เป็นแบบ generational อ็อบเจกต์วัยเยาว์อยู่ใน *nursery* ซึ่งถูกเก็บกวาดบ่อยและรวดเร็ว (scavenge) ผู้รอดชีวิตจะถูกเลื่อนไปสู่ generation เก่า ซึ่งจัดการด้วย mark-and-sweep แบบ incremental การรั่วไหลแบบคลาสสิกไม่ได้มาจาก GC แต่มาจากการอ้างอิงที่คุณลืม: แคชที่อิง Map, listener ที่ไม่เคยถูกถอด, closure ที่จับอ็อบเจกต์ขนาดใหญ่ไว้
const strongCache = new Map();
const weakCache = new WeakMap();
let session = { user: "ada" };
strongCache.set(session, "data");
weakCache.set(session, "data");
session = null;
// Map: the key retains the object -> memory leak
// WeakMap: the entry becomes collectableWeakMap ไม่ยึดคีย์ของมันไว้ เมื่อคีย์กลายเป็นเข้าถึงไม่ได้จากที่อื่นใด ทั้งรายการจะหายไป ผลที่ตามมาต่อการออกแบบ: คีย์ต้องเป็น อ็อบเจกต์ (หรือ symbol ที่ไม่ได้ลงทะเบียน) และโครงสร้างนี้ ทั้งวนซ้ำไม่ได้และวัดขนาดไม่ได้ การเปิดเผยขนาดของมันจะเผยพฤติกรรมของ GC ซึ่งเป็นแบบ non-deterministic WeakRef ไปไกลกว่านั้น มันให้การอ้างอิงแบบ weak โดยตรงแก่คุณ ซึ่งต้อง dereference ด้วย deref()
let config = { theme: "dark" };
const ref = new WeakRef(config);
function readTheme() {
const target = ref.deref();
return target ? target.theme : "default";
}
console.log(readTheme()); // "dark"
config = null;
// After a GC pass, deref() may
// return undefined: plan a fallback.ในด้านประสิทธิภาพที่รับรู้ได้ event ที่ความถี่สูง (scroll, resize, input, mousemove) สามารถทริกเกอร์การเรียกได้หลายร้อยครั้งต่อวินาที มีสองกลยุทธ์ที่เสริมกัน: debounce จะรันฟังก์ชันหลังช่วงเงียบเท่านั้น (เหมาะสำหรับการค้นหาขณะพิมพ์) ในขณะที่ throttle รับประกันการทำงานอย่างมากหนึ่งครั้งต่อช่วงเวลา (เหมาะสำหรับติดตามการเลื่อน)
function debounce(fn, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(
() => fn.apply(this, args),
delay
);
};
}function throttle(fn, interval) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= interval) {
last = now;
fn.apply(this, args);
}
};
}ref.deref() บน WeakRef สามารถคืนค่าอะไรได้บ้าง?undefined ถ้ามันถูกเก็บกวาดไปแล้วnull ถ้าอ็อบเจกต์ถูกเก็บกวาดไปแล้ว