คุณสมบัติ "ที่ซ่อนอยู่" ของภาษา
บทความนี้ครอบคลุมหัวข้อที่เจาะจงแคบมาก ซึ่งนักพัฒนาส่วนใหญ่ไม่ค่อยพบในทางปฏิบัติมากนัก (และอาจไม่รู้ด้วยซ้ำว่ามีอยู่จริง)
เราขอแนะนำให้ข้ามบทนี้หากคุณเพิ่งเริ่มเรียนรู้ JavaScript
เมื่อนึกถึงแนวคิดพื้นฐานของ หลักการความสามารถในการเข้าถึง จากบทการรวบรวมขยะ เราสามารถสังเกตได้ว่าเอ็นจิ้น JavaScript รับประกันว่าจะเก็บค่าไว้ในหน่วยความจำที่สามารถเข้าถึงได้หรือใช้งานอยู่
ตัวอย่างเช่น:
// ตัวแปรผู้ใช้มีการอ้างอิงที่ชัดเจนไปยังวัตถุ ให้ผู้ใช้ = { ชื่อ: "จอห์น" }; // มาเขียนทับค่าของตัวแปรผู้ใช้กันดีกว่า ผู้ใช้ = โมฆะ; // การอ้างอิงสูญหายและวัตถุจะถูกลบออกจากหน่วยความจำ
หรือโค้ดที่คล้ายกันแต่ซับซ้อนกว่าเล็กน้อยพร้อมการอ้างอิงที่แข็งแกร่งสองรายการ:
// ตัวแปรผู้ใช้มีการอ้างอิงที่ชัดเจนไปยังวัตถุ ให้ผู้ใช้ = { ชื่อ: "จอห์น" }; // คัดลอกการอ้างอิงที่รัดกุมไปยังวัตถุลงในตัวแปรผู้ดูแลระบบ ให้ผู้ดูแลระบบ = ผู้ใช้; // มาเขียนทับค่าของตัวแปรผู้ใช้กันดีกว่า ผู้ใช้ = โมฆะ; // วัตถุยังคงสามารถเข้าถึงได้ผ่านตัวแปรผู้ดูแลระบบ
ออบเจ็กต์ { name: "John" }
จะถูกลบออกจากหน่วยความจำหากไม่มีการอ้างอิงที่ชัดเจนเท่านั้น (หากเราเขียนทับค่าของตัวแปร admin
ด้วย)
ใน JavaScript มีแนวคิดที่เรียกว่า WeakRef
ซึ่งมีการทำงานแตกต่างออกไปเล็กน้อยในกรณีนี้
เงื่อนไข: “การอ้างอิงที่ชัดเจน”, “การอ้างอิงที่อ่อนแอ”
การอ้างอิงที่รัดกุม - คือการอ้างอิงถึงวัตถุหรือค่าที่ป้องกันไม่ให้ตัวรวบรวมขยะลบออก ดังนั้นการรักษาวัตถุหรือค่าไว้ในหน่วยความจำที่ชี้ไป
ซึ่งหมายความว่าอ็อบเจ็กต์หรือค่ายังคงอยู่ในหน่วยความจำและตัวรวบรวมขยะจะไม่ถูกรวบรวมตราบใดที่มีการอ้างอิงที่ชัดเจนที่ใช้งานอยู่
ใน JavaScript การอ้างอิงทั่วไปถึงอ็อบเจ็กต์ถือเป็นการอ้างอิงที่รัดกุม ตัวอย่างเช่น:
// ตัวแปรผู้ใช้มีการอ้างอิงที่ชัดเจนไปยังวัตถุนี้ ให้ผู้ใช้ = { ชื่อ: "จอห์น" };
การอ้างอิงที่อ่อนแอ - คือการอ้างอิงถึงวัตถุหรือค่า ซึ่ง ไม่ ได้ป้องกันไม่ให้ตัวรวบรวมขยะลบออก ตัวรวบรวมขยะสามารถลบอ็อบเจ็กต์หรือค่าได้ ถ้าการอ้างอิงที่เหลืออยู่เพียงการอ้างอิงที่ไม่รัดกุม
หมายเหตุข้อควรระวัง
ก่อนที่เราจะเจาะลึกเรื่องนี้ เป็นที่น่าสังเกตว่าการใช้โครงสร้างที่ถูกต้องที่กล่าวถึงในบทความนี้ต้องใช้ความคิดอย่างรอบคอบ และควรหลีกเลี่ยงหากเป็นไปได้
WeakRef
– คือออบเจ็กต์ที่มีการอ้างอิงที่ไม่ชัดเจนไปยังออบเจ็กต์อื่น เรียกว่า target
หรือ referent
ลักษณะเฉพาะของ WeakRef
คือ มันไม่ได้ป้องกันตัวรวบรวมขยะจากการลบวัตถุอ้างอิง กล่าวอีกนัยหนึ่ง วัตถุ WeakRef
จะไม่ทำให้วัตถุ referent
ยังคงอยู่
ตอนนี้ให้นำตัวแปร user
เป็น "ผู้อ้างอิง" และสร้างการอ้างอิงที่อ่อนแอจากนั้นไปยังตัวแปร admin
หากต้องการสร้างการอ้างอิงที่ไม่รัดกุม คุณจะต้องใช้ตัวสร้าง WeakRef
โดยส่งผ่านวัตถุเป้าหมาย (วัตถุที่คุณต้องการให้มีการอ้างอิงที่ไม่รัดกุม)
ในกรณีของเรา — นี่คือตัวแปร user
:
// ตัวแปรผู้ใช้มีการอ้างอิงที่ชัดเจนไปยังวัตถุ ให้ผู้ใช้ = { ชื่อ: "จอห์น" }; // ตัวแปรผู้ดูแลระบบมีการอ้างอิงที่อ่อนแอไปยังวัตถุ ให้ผู้ดูแลระบบ = WeakRef ใหม่ (ผู้ใช้);
แผนภาพด้านล่างแสดงการอ้างอิงสองประเภท: การอ้างอิงที่รัดกุมโดยใช้ตัวแปร user
และการอ้างอิงที่รัดกุมโดยใช้ตัวแปร admin
:
จากนั้น เมื่อถึงจุดหนึ่ง เราก็หยุดใช้ตัวแปร user
ซึ่งจะถูกเขียนทับ อยู่นอกขอบเขต ฯลฯ ในขณะที่เก็บอินสแตนซ์ WeakRef
ไว้ในตัวแปร admin
:
// มาเขียนทับค่าของตัวแปรผู้ใช้กันดีกว่า ผู้ใช้ = โมฆะ;
การอ้างอิงถึงวัตถุอย่างไม่ชัดเจนนั้นไม่เพียงพอที่จะทำให้วัตถุนั้น "มีชีวิต" เมื่อการอ้างอิงออบเจ็กต์ที่เหลือเพียงการอ้างอิงเดียวคือการอ้างอิงที่ไม่ชัดเจน ตัวรวบรวมขยะมีอิสระที่จะทำลายออบเจ็กต์นี้และใช้หน่วยความจำเพื่อสิ่งอื่น
อย่างไรก็ตาม จนกว่าวัตถุจะถูกทำลายจริง การอ้างอิงที่ไม่รัดกุมอาจส่งคืนวัตถุนั้น แม้ว่าจะไม่มีการอ้างอิงที่รัดกุมไปยังวัตถุนี้อีกต่อไป นั่นคือวัตถุของเรากลายเป็น "แมวของชโรดิงเงอร์" เราไม่สามารถรู้ได้อย่างแน่ชัดว่ามัน "เป็น" หรือ "ตายแล้ว":
ณ จุดนี้ หากต้องการรับออบเจ็กต์จากอินสแตนซ์ WeakRef
เราจะใช้เมธอด deref()
เมธอด deref()
ส่งคืนอ็อบเจ็กต์อ้างอิงที่ WeakRef
ชี้ไป หากอ็อบเจ็กต์ยังอยู่ในหน่วยความจำ หากวัตถุถูกลบโดยตัวรวบรวมขยะ ดังนั้นเมธอด deref()
จะส่งคืน undefined
:
ให้ ref = admin.deref(); ถ้า (อ้างอิง) { // วัตถุยังสามารถเข้าถึงได้: เราสามารถดำเนินการจัดการใด ๆ กับมันได้ } อื่น { // วัตถุถูกรวบรวมโดยคนเก็บขยะ -
โดยทั่วไปแล้ว WeakRef
จะใช้เพื่อสร้างแคชหรืออาเรย์แบบเชื่อมโยงที่เก็บอ็อบเจ็กต์ที่ใช้ทรัพยากรจำนวนมาก วิธีนี้ช่วยให้สามารถหลีกเลี่ยงการป้องกันไม่ให้ตัวรวบรวมขยะรวบรวมวัตถุเหล่านี้โดยขึ้นอยู่กับการมีอยู่ของวัตถุเหล่านี้ในแคชหรืออาเรย์ที่เชื่อมโยงเท่านั้น
หนึ่งในตัวอย่างหลักคือสถานการณ์ที่เรามีออบเจ็กต์รูปภาพไบนารีจำนวนมาก (เช่น แสดงเป็น ArrayBuffer
หรือ Blob
) และเราต้องการเชื่อมโยงชื่อหรือเส้นทางกับแต่ละรูปภาพ โครงสร้างข้อมูลที่มีอยู่ไม่ค่อยเหมาะสมกับวัตถุประสงค์เหล่านี้:
การใช้ Map
เพื่อสร้างความสัมพันธ์ระหว่างชื่อและรูปภาพ หรือในทางกลับกัน จะเก็บวัตถุรูปภาพไว้ในหน่วยความจำเนื่องจากมีอยู่ใน Map
เป็นคีย์หรือค่า
WeakMap
ไม่มีสิทธิ์สำหรับเป้าหมายนี้เช่นกัน เนื่องจากออบเจ็กต์ที่แสดงเป็นคีย์ WeakMap
ใช้การอ้างอิงที่ไม่รัดกุม และไม่ได้รับการปกป้องจากการลบโดยตัวรวบรวมขยะ
แต่ในสถานการณ์นี้ เราจำเป็นต้องมีโครงสร้างข้อมูลที่จะใช้การอ้างอิงที่ไม่รัดกุมในค่าของมัน
เพื่อจุดประสงค์นี้ เราสามารถใช้คอลเลกชัน Map
ซึ่งมีค่าเป็นอินสแตนซ์ WeakRef
ซึ่งอ้างอิงถึงวัตถุขนาดใหญ่ที่เราต้องการ ดังนั้นเราจะไม่เก็บวัตถุขนาดใหญ่และไม่จำเป็นเหล่านี้ไว้ในหน่วยความจำนานเกินกว่าที่ควรจะเป็น
มิฉะนั้น นี่เป็นวิธีรับออบเจ็กต์รูปภาพจากแคชหากยังสามารถเข้าถึงได้ หากมีการรวบรวมขยะ เราจะสร้างใหม่หรือดาวน์โหลดใหม่อีกครั้ง
วิธีนี้จะทำให้ใช้หน่วยความจำน้อยลงในบางสถานการณ์
ด้านล่างนี้คือข้อมูลโค้ดที่สาธิตเทคนิคการใช้ WeakRef
กล่าวโดยสรุป เราใช้ Map
พร้อมด้วยคีย์สตริงและอ็อบเจ็กต์ WeakRef
เป็นค่าของมัน หากตัวรวบรวมขยะไม่ได้รวบรวมวัตถุ WeakRef
เราจะรับมันจากแคช มิฉะนั้น เราจะดาวน์โหลดใหม่อีกครั้งและเก็บไว้ในแคชเพื่อนำกลับมาใช้ใหม่ได้อีก:
ฟังก์ชั่น fetchImg() { // ฟังก์ชันนามธรรมสำหรับการดาวน์โหลดรูปภาพ... - ฟังก์ชั่นอ่อนแอRefCache (fetchImg) { // (1) const imgCache = แผนที่ใหม่ (); // (2) กลับ (imgName) => { // (3) const cachedImg = imgCache.get(imgName); // (4) ถ้า (cachedImg?.deref()) { // (5) กลับ cachedImg?.deref(); - const newImg = fetchImg (imgName); // (6) imgCache.set(imgName, WeakRef ใหม่ (newImg)); // (7) กลับ newImg; - - const getCachedImg = อ่อนแอRefCache (fetchImg);
มาเจาะลึกรายละเอียดของสิ่งที่เกิดขึ้นที่นี่:
weakRefCache
– เป็นฟังก์ชันลำดับที่สูงกว่าที่รับฟังก์ชันอื่น fetchImg
เป็นอาร์กิวเมนต์ ในตัวอย่างนี้ เราอาจละเลยคำอธิบายโดยละเอียดของฟังก์ชัน fetchImg
ได้ เนื่องจากอาจเป็นตรรกะในการดาวน์โหลดรูปภาพก็ได้
imgCache
– คือแคชของรูปภาพที่เก็บผลลัพธ์ที่แคชไว้ของฟังก์ชัน fetchImg
ในรูปแบบของคีย์สตริง (ชื่อรูปภาพ) และอ็อบเจ็กต์ WeakRef
เป็นค่า
ส่งกลับฟังก์ชันที่ไม่ระบุชื่อที่ใช้ชื่อรูปภาพเป็นอาร์กิวเมนต์ อาร์กิวเมนต์นี้จะถูกใช้เป็นคีย์สำหรับรูปภาพที่แคชไว้
กำลังพยายามรับผลลัพธ์แคชจากแคช โดยใช้คีย์ที่ให้มา (ชื่อรูปภาพ)
หากแคชมีค่าสำหรับคีย์ที่ระบุ และวัตถุ WeakRef
ไม่ได้ถูกลบโดยตัวรวบรวมขยะ ให้ส่งคืนผลลัพธ์ที่แคชไว้
หากไม่มีรายการในแคชที่มีคีย์ที่ร้องขอ หรือเมธอด deref()
ส่งกลับ undefined
(หมายความว่าอ็อบเจ็กต์ WeakRef
ถูกรวบรวมแบบขยะ) ฟังก์ชัน fetchImg
จะดาวน์โหลดรูปภาพอีกครั้ง
ใส่ภาพที่ดาวน์โหลดลงในแคชเป็นวัตถุ WeakRef
ตอนนี้เรามีคอลเลกชัน Map
โดยที่คีย์คือชื่อรูปภาพเป็นสตริง และค่าคือออบเจ็กต์ WeakRef
ที่บรรจุรูปภาพเหล่านั้นไว้
เทคนิคนี้ช่วยหลีกเลี่ยงการจัดสรรหน่วยความจำจำนวนมากสำหรับอ็อบเจ็กต์ที่ใช้ทรัพยากรมากซึ่งไม่มีใครใช้อีกต่อไป นอกจากนี้ยังช่วยประหยัดหน่วยความจำและเวลาในกรณีที่นำออบเจ็กต์แคชกลับมาใช้ใหม่
นี่คือการแสดงภาพว่าโค้ดนี้มีลักษณะอย่างไร:
แต่การใช้งานนี้มีข้อเสีย: เมื่อเวลาผ่านไป Map
จะถูกเติมด้วยสตริงเป็นคีย์ ซึ่งชี้ไปที่ WeakRef
ซึ่งมีอ็อบเจ็กต์อ้างอิงได้ถูกรวบรวมขยะแล้ว:
วิธีหนึ่งในการจัดการปัญหานี้คือการไล่แคชเป็นระยะและล้างรายการที่ "เสีย" ออก อีกวิธีหนึ่งคือการใช้ตัวสรุป ซึ่งเราจะสำรวจต่อไป
กรณีการใช้งานอื่นสำหรับ WeakRef
– คือการติดตามวัตถุ DOM
ลองจินตนาการถึงสถานการณ์ที่โค้ดหรือไลบรารีของบุคคลที่สามโต้ตอบกับองค์ประกอบบนเพจของเราตราบใดที่ยังมีอยู่ใน DOM ตัวอย่างเช่น อาจเป็นยูทิลิตี้ภายนอกสำหรับการตรวจสอบและแจ้งเตือนเกี่ยวกับสถานะของระบบ (โดยทั่วไปเรียกว่า “คนบันทึก” – โปรแกรมที่ส่งข้อความข้อมูลที่เรียกว่า “บันทึก”)
ตัวอย่างเชิงโต้ตอบ:
ผลลัพธ์
ดัชนี js
ดัชนี.css
ดัชนี.html
const startMessagesBtn = document.querySelector('.start-messages'); // (1) const closeWindowBtn = document.querySelector('.window__button'); // (2) const windowElementRef = ใหม่ WeakRef(document.querySelector(".window__body")); // (3) startMessagesBtn.addEventListener('คลิก', () => { // (4) startMessages(windowElementRef); startMessagesBtn.disabled = จริง; - closeWindowBtn.addEventListener('คลิก', () => document.querySelector(".window__body").remove()); // (5) const startMessages = (องค์ประกอบ) => { const timerId = setInterval(() => { // (6) ถ้า (element.deref()) { // (7) น้ำหนักบรรทุก const = document.createElement("p"); payload.textContent = `ข้อความ: สถานะของระบบ ตกลง: ${new Date().toLocaleTimeString()}`; element.deref().append(เพย์โหลด); } อื่น ๆ { // (8) alert("องค์ประกอบถูกลบแล้ว"); // (9) clearInterval(รหัสตัวจับเวลา); - }, 1,000); -
.แอป { จอแสดงผล: ดิ้น; ทิศทางแบบยืดหยุ่น: คอลัมน์; ช่องว่าง: 16px; - .ข้อความเริ่มต้น { ความกว้าง: เนื้อหาพอดี; - .หน้าต่าง { ความกว้าง: 100%; เส้นขอบ: 2px ทึบ #464154; ล้น: ซ่อนเร้น; - .window__header { ตำแหน่ง: เหนียว; ช่องว่างภายใน: 8px; จอแสดงผล: ดิ้น; ปรับเนื้อหา: ช่องว่างระหว่าง; จัดรายการ: กึ่งกลาง; สีพื้นหลัง: #736e7e; - .window__title { ระยะขอบ: 0; ขนาดตัวอักษร: 24px; น้ำหนักตัวอักษร: 700; สี: ขาว; การเว้นวรรคตัวอักษร: 1px; - .window__button { ช่องว่างภายใน: 4px; พื้นหลัง: #4f495c; โครงร่าง: ไม่มี; เส้นขอบ: 2px ทึบ #464154; สี: ขาว; ขนาดตัวอักษร: 16px; เคอร์เซอร์: ตัวชี้; - .window__body { ความสูง: 250px; ช่องว่างภายใน: 16px; ล้น: เลื่อน; สีพื้นหลัง: #736e7e33; -
<!DOCTYPE HTML> <html lang="th"> <หัว> <meta charset="utf-8"> <link rel="stylesheet" href="index.css"> <title>WeakRef DOM Logger</title> </หัว> <ร่างกาย> <คลาส div = "แอป"> <button class="start-messages">เริ่มส่งข้อความ</button> <คลาส div="หน้าต่าง"> <div class="window__header"> <p class="window__title">ข้อความ:</p> <button class="window__button">ปิด</button> </div> <div class="window__body"> ไม่มีข้อความ </div> </div> </div> <script type="โมดูล" src="index.js"></script> </ร่างกาย> </html>
เมื่อคลิกปุ่ม “เริ่มส่งข้อความ” ในส่วนที่เรียกว่า “หน้าต่างแสดงบันทึก” (องค์ประกอบที่มีคลาส .window__body
) ข้อความ (บันทึก) จะเริ่มปรากฏขึ้น
แต่ทันทีที่องค์ประกอบนี้ถูกลบออกจาก DOM คนตัดไม้ควรหยุดส่งข้อความ หากต้องการทำซ้ำการลบองค์ประกอบนี้ เพียงคลิกปุ่ม "ปิด" ที่มุมขวาบน
เพื่อไม่ให้งานของเราซับซ้อนและไม่แจ้งเตือนรหัสบุคคลที่สามทุกครั้งที่องค์ประกอบ DOM ของเราพร้อมใช้งาน และเมื่อไม่พร้อมใช้งาน การสร้างการอ้างอิงที่อ่อนแอไปยังองค์ประกอบนั้นโดยใช้ WeakRef
ก็เพียงพอแล้ว
เมื่อองค์ประกอบถูกลบออกจาก DOM คนตัดไม้จะสังเกตเห็นและหยุดส่งข้อความ
ตอนนี้เรามาดูซอร์สโค้ดให้ละเอียดยิ่งขึ้น ( tab index.js
):
รับองค์ประกอบ DOM ของปุ่ม "เริ่มส่งข้อความ"
รับองค์ประกอบ DOM ของปุ่ม "ปิด"
รับองค์ประกอบ DOM ของหน้าต่างแสดงบันทึกโดยใช้ตัวสร้าง new WeakRef()
ด้วยวิธีนี้ ตัวแปร windowElementRef
จะเก็บการอ้างอิงที่อ่อนแอไปยังองค์ประกอบ DOM
เพิ่มผู้ฟังเหตุการณ์บนปุ่ม "เริ่มส่งข้อความ" ซึ่งรับผิดชอบในการเริ่มตัวบันทึกเมื่อคลิก
เพิ่มผู้ฟังเหตุการณ์บนปุ่ม "ปิด" ซึ่งรับผิดชอบในการปิดหน้าต่างแสดงบันทึกเมื่อคลิก
ใช้ setInterval
เพื่อเริ่มแสดงข้อความใหม่ทุกวินาที
หากองค์ประกอบ DOM ของหน้าต่างแสดงบันทึกยังคงสามารถเข้าถึงได้และเก็บไว้ในหน่วยความจำ ให้สร้างและส่งข้อความใหม่
ถ้าเมธอด deref()
ส่งคืน undefined
แสดงว่าองค์ประกอบ DOM ถูกลบออกจากหน่วยความจำแล้ว ในกรณีนี้ ตัวบันทึกจะหยุดแสดงข้อความและล้างตัวจับเวลา
alert
ซึ่งจะถูกเรียกหลังจากองค์ประกอบ DOM ของหน้าต่างแสดงบันทึกถูกลบออกจากหน่วยความจำ (เช่น หลังจากคลิกปุ่ม "ปิด") โปรดทราบว่าการลบออกจากหน่วยความจำอาจไม่เกิดขึ้นทันที เนื่องจากขึ้นอยู่กับกลไกภายในของตัวรวบรวมขยะเท่านั้น
เราไม่สามารถควบคุมกระบวนการนี้ได้โดยตรงจากโค้ด อย่างไรก็ตาม แม้ว่าจะเป็นเช่นนี้ เรายังคงมีตัวเลือกในการบังคับให้รวบรวมขยะจากเบราว์เซอร์
ตัวอย่างเช่น ใน Google Chrome ในการดำเนินการนี้ คุณจะต้องเปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ ( Ctrl + Shift + J บน Windows/Linux หรือ Option + ⌘ + J บน macOS) ไปที่แท็บ "ประสิทธิภาพ" และคลิกที่ ปุ่มไอคอนถังขยะ - “เก็บขยะ”:
ฟังก์ชันนี้รองรับในเบราว์เซอร์สมัยใหม่ส่วนใหญ่ หลังจากดำเนินการแล้ว alert
จะเริ่มทำงานทันที
ตอนนี้ถึงเวลาพูดคุยเกี่ยวกับผู้เข้ารอบสุดท้ายแล้ว ก่อนที่เราจะไปต่อ เรามาทำความเข้าใจคำศัพท์กันก่อน:
Cleanup callback (finalizer) - เป็นฟังก์ชันที่ดำเนินการเมื่อวัตถุที่ลงทะเบียนใน FinalizationRegistry
ถูกลบออกจากหน่วยความจำโดยตัวรวบรวมขยะ
วัตถุประสงค์คือเพื่อให้มีความสามารถในการดำเนินการเพิ่มเติมที่เกี่ยวข้องกับวัตถุ หลังจากที่ถูกลบออกจากหน่วยความจำในที่สุด
Registry (หรือ FinalizationRegistry
) - เป็นอ็อบเจ็กต์พิเศษใน JavaScript ที่จัดการการลงทะเบียนและการยกเลิกการลงทะเบียนอ็อบเจ็กต์และการเรียกกลับการล้างข้อมูล
กลไกนี้อนุญาตให้ลงทะเบียนออบเจ็กต์เพื่อติดตามและเชื่อมโยงการโทรกลับการล้างข้อมูลด้วย โดยพื้นฐานแล้ว มันเป็นโครงสร้างที่เก็บข้อมูลเกี่ยวกับออบเจ็กต์ที่ลงทะเบียนและการเรียกกลับการล้างข้อมูล จากนั้นจะเรียกใช้การเรียกกลับเหล่านั้นโดยอัตโนมัติเมื่อออบเจ็กต์ถูกลบออกจากหน่วยความจำ
หากต้องการสร้างอินสแตนซ์ของ FinalizationRegistry
จะต้องเรียก Constructor ซึ่งรับอาร์กิวเมนต์เดียว นั่นคือการเรียกกลับการล้างข้อมูล (finalizer)
ไวยากรณ์:
ฟังก์ชั่น cleanupCallback (heldValue) { // ล้างรหัสโทรกลับ - const Registry = FinalizationRegistry ใหม่ (cleanupCallback);
ที่นี่:
cleanupCallback
– การโทรกลับเพื่อล้างข้อมูลที่จะถูกเรียกโดยอัตโนมัติเมื่อวัตถุที่ลงทะเบียนถูกลบออกจากหน่วยความจำ
heldValue
– ค่าที่ถูกส่งเป็นอาร์กิวเมนต์ไปยังการเรียกกลับการล้างข้อมูล ถ้า heldValue
เป็นออบเจ็กต์ รีจิสทรีจะเก็บการอ้างอิงที่ชัดเจนไว้
registry
– ตัวอย่างของ FinalizationRegistry
วิธีการ FinalizationRegistry
:
register(target, heldValue [, unregisterToken])
– ใช้เพื่อลงทะเบียนอ็อบเจ็กต์ในรีจิสตรี
target
– วัตถุที่กำลังลงทะเบียนสำหรับการติดตาม หาก target
คือการรวบรวมขยะ การเรียกกลับการล้างข้อมูลจะถูกเรียกพร้อมกับ heldValue
เป็นอาร์กิวเมนต์
ตัวเลือก unregisterToken
– โทเค็นการยกเลิกการลงทะเบียน สามารถส่งผ่านเพื่อยกเลิกการลงทะเบียนออบเจ็กต์ก่อนที่ตัวรวบรวมขยะจะลบออก โดยทั่วไปแล้ว ออบเจ็กต์ target
จะถูกใช้เป็น unregisterToken
ซึ่งเป็นแนวทางปฏิบัติมาตรฐาน
unregister(unregisterToken)
- วิธี unregister
ใช้เพื่อยกเลิกการลงทะเบียนวัตถุจากรีจิสทรี ต้องใช้หนึ่งอาร์กิวเมนต์ - unregisterToken
(โทเค็นที่ไม่ได้ลงทะเบียนที่ได้รับเมื่อลงทะเบียนวัตถุ)
ตอนนี้เรามาดูตัวอย่างง่ายๆ กัน ลองใช้วัตถุ user
ที่รู้จักอยู่แล้วและสร้างอินสแตนซ์ของ FinalizationRegistry
:
ให้ผู้ใช้ = { ชื่อ: "จอห์น" }; const Registry = FinalizationRegistry ใหม่ ((heldValue) => { console.log(`${heldValue} ถูกรวบรวมโดยตัวรวบรวมขยะ.`); -
จากนั้น เราจะลงทะเบียนออบเจ็กต์ที่ต้องมีการโทรกลับเพื่อล้างข้อมูลโดยการเรียกเมธอด register
:
register.register(ผู้ใช้, ชื่อผู้ใช้.);
รายการทะเบียนไม่ได้เก็บการอ้างอิงที่ชัดเจนถึงวัตถุที่กำลังลงทะเบียน เนื่องจากจะทำให้วัตถุประสงค์ของรายการทะเบียนเสียไป หากรีจิสทรีมีการอ้างอิงที่ชัดเจน วัตถุนั้นจะไม่ถูกรวบรวมแบบขยะ
หากวัตถุถูกลบโดยตัวรวบรวมขยะ การเรียกกลับการล้างข้อมูลของเราอาจถูกเรียกในอนาคต โดยจะส่ง heldValue
ไว้ไป:
// เมื่อวัตถุผู้ใช้ถูกลบโดยตัวรวบรวมขยะ ข้อความต่อไปนี้จะถูกพิมพ์ในคอนโซล: “จอห์นถูกคนเก็บขยะเก็บไป”
นอกจากนี้ยังมีสถานการณ์ที่แม้แต่ในการใช้งานที่ใช้การเรียกกลับการล้างข้อมูล ก็มีโอกาสที่จะไม่ถูกเรียก
ตัวอย่างเช่น:
เมื่อโปรแกรมยุติการทำงานโดยสมบูรณ์ (เช่น เมื่อปิดแท็บในเบราว์เซอร์)
เมื่ออินสแตนซ์ FinalizationRegistry
ไม่สามารถเข้าถึงได้ด้วยโค้ด JavaScript อีกต่อไป หากออบเจ็กต์ที่สร้างอินสแตนซ์ FinalizationRegistry
อยู่นอกขอบเขตหรือถูกลบ การเรียกกลับการล้างข้อมูลที่ลงทะเบียนในรีจิสทรีนั้นก็อาจไม่ถูกเรียกใช้เช่นกัน
กลับมาที่ตัวอย่างแคช ที่อ่อนแอ ของเรา เราจะสังเกตเห็นสิ่งต่อไปนี้:
แม้ว่าค่าที่ห่อไว้ใน WeakRef
จะถูกรวบรวมโดยตัวรวบรวมขยะ แต่ก็ยังมีปัญหาของ "หน่วยความจำรั่ว" ในรูปแบบของคีย์ที่เหลือ ซึ่งค่านั้นได้ถูกรวบรวมโดยตัวรวบรวมขยะแล้ว
นี่คือตัวอย่างแคชที่ได้รับการปรับปรุงโดยใช้ FinalizationRegistry
:
ฟังก์ชั่น fetchImg() { // ฟังก์ชันนามธรรมสำหรับการดาวน์โหลดรูปภาพ... - ฟังก์ชั่นอ่อนแอRefCache (fetchImg) { const imgCache = แผนที่ใหม่ (); const register = new FinalizationRegistry((imgName) => { // (1) const cachedImg = imgCache.get(imgName); ถ้า (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); - กลับ (imgName) => { const cachedImg = imgCache.get(imgName); ถ้า (cachedImg?.deref()) { กลับ cachedImg?.deref(); - const newImg = fetchImg (imgName); imgCache.set(imgName, WeakRef ใหม่ (newImg)); register.register(newImg, imgName); // (2) กลับ newImg; - - const getCachedImg = อ่อนแอRefCache (fetchImg);
ในการจัดการการล้างรายการแคชที่ "ใช้งานไม่ได้" เมื่อตัวรวบรวมวัตถุ WeakRef
ที่เกี่ยวข้องถูกรวบรวมโดยตัวรวบรวมขยะ เราจะสร้างรีจิสทรีการล้างข้อมูล FinalizationRegistry
จุดสำคัญที่นี่คือ ควรตรวจสอบในการเรียกกลับการล้างข้อมูล หากรายการถูกลบโดยตัวรวบรวมขยะและไม่ได้เพิ่มอีกครั้ง เพื่อไม่ให้ลบรายการ "สด"
เมื่อดาวน์โหลดค่าใหม่ (รูปภาพ) และใส่ลงในแคชแล้ว เราจะลงทะเบียนค่านั้นในรีจิสทรีตัวสุดท้ายเพื่อติดตามออบเจ็กต์ WeakRef
การใช้งานนี้ประกอบด้วยคู่คีย์/ค่าจริงหรือ "สด" เท่านั้น ในกรณีนี้ แต่ละออบเจ็กต์ WeakRef
จะถูกลงทะเบียนใน FinalizationRegistry
และหลังจากที่วัตถุถูกล้างข้อมูลโดยตัวรวบรวมขยะ การเรียกกลับการล้างข้อมูลจะลบค่า undefined
ทั้งหมด
นี่คือการแสดงภาพของโค้ดที่อัปเดต:
ลักษณะสำคัญของการใช้งานที่ได้รับการอัปเดตก็คือผู้สรุปอนุญาตให้สร้างกระบวนการแบบขนานระหว่างโปรแกรม "หลัก" และการเรียกกลับการล้างข้อมูล ในบริบทของ JavaScript โปรแกรม “หลัก” คือโค้ด JavaScript ของเราที่ทำงานและดำเนินการในแอปพลิเคชันหรือหน้าเว็บของเรา
ดังนั้น นับตั้งแต่วินาทีที่วัตถุถูกทำเครื่องหมายเพื่อลบโดยตัวรวบรวมขยะ และจนถึงการดำเนินการจริงของการโทรกลับการล้างข้อมูล อาจมีช่องว่างของเวลาที่แน่นอน สิ่งสำคัญคือต้องเข้าใจว่าในช่วงเวลาดังกล่าว โปรแกรมหลักสามารถทำการเปลี่ยนแปลงใดๆ กับวัตถุหรือแม้แต่นำมันกลับไปยังหน่วยความจำได้
นั่นเป็นเหตุผลว่าทำไมในการเรียกกลับการล้างข้อมูล เราต้องตรวจสอบเพื่อดูว่ารายการถูกเพิ่มกลับเข้าไปในแคชโดยโปรแกรมหลักหรือไม่ เพื่อหลีกเลี่ยงการลบรายการ "สด" ในทำนองเดียวกัน เมื่อค้นหาคีย์ในแคช มีโอกาสที่ค่าจะถูกลบโดยตัวรวบรวมขยะ แต่ยังไม่ได้ดำเนินการเรียกกลับการล้างข้อมูล
สถานการณ์ดังกล่าวจำเป็นต้องได้รับการดูแลเป็นพิเศษหากคุณกำลังทำงานกับ FinalizationRegistry
จากทฤษฎีไปสู่การปฏิบัติ ลองจินตนาการถึงสถานการณ์ในชีวิตจริงที่ผู้ใช้ซิงโครไนซ์รูปภาพของตนบนอุปกรณ์เคลื่อนที่กับบริการคลาวด์บางอย่าง (เช่น iCloud หรือ Google Photos) และต้องการดูรูปภาพเหล่านั้นจากอุปกรณ์อื่นๆ นอกเหนือจากฟังก์ชันพื้นฐานในการดูภาพถ่ายแล้ว บริการดังกล่าวยังมีคุณลักษณะเพิ่มเติมอีกมากมาย เช่น:
การแก้ไขภาพถ่ายและเอฟเฟกต์วิดีโอ
สร้าง “ความทรงจำ” และอัลบั้ม
การตัดต่อวิดีโอจากชุดภาพถ่าย
…และอีกมากมาย
ต่อไปนี้เป็นตัวอย่าง เราจะใช้บริการดังกล่าวแบบที่ค่อนข้างดั้งเดิม ประเด็นหลักคือการแสดงสถานการณ์ที่เป็นไปได้ของการใช้ WeakRef
และ FinalizationRegistry
ร่วมกันในชีวิตจริง
นี่คือสิ่งที่ดูเหมือน:
ทางด้านซ้ายมีคลังรูปภาพบนคลาวด์ (แสดงเป็นรูปขนาดย่อ) เราสามารถเลือกภาพที่เราต้องการและสร้างภาพต่อกันได้โดยคลิกปุ่ม "สร้างภาพต่อกัน" ที่ด้านขวาของหน้า จากนั้นจึงสามารถดาวน์โหลดภาพตัดปะที่ได้เป็นรูปภาพได้
หากต้องการเพิ่มความเร็วในการโหลดหน้าเว็บ การดาวน์โหลดและแสดงภาพขนาดย่อในคุณภาพ การบีบอัด จึงสมเหตุสมผล แต่หากต้องการสร้างภาพต่อกันจากรูปภาพที่เลือก ให้ดาวน์โหลดและใช้งานในคุณภาพ ขนาดเต็ม
ด้านล่างนี้เราจะเห็นได้ว่าขนาดที่แท้จริงของภาพขนาดย่อคือ 240x240 พิกเซล ขนาดถูกเลือกโดยมีวัตถุประสงค์เพื่อเพิ่มความเร็วในการโหลด ยิ่งกว่านั้น เราไม่ต้องการภาพถ่ายขนาดเต็มในโหมดแสดงตัวอย่าง
สมมติว่าเราต้องสร้างภาพต่อกัน 4 ภาพ โดยเลือกภาพเหล่านั้นแล้วคลิกปุ่ม "สร้างภาพต่อกัน" ในขั้นตอนนี้ ฟังก์ชัน weakRefCache
ที่เราทราบอยู่แล้วจะตรวจสอบว่าอิมเมจที่ต้องการอยู่ในแคชหรือไม่ ถ้าไม่เช่นนั้น มันจะดาวน์โหลดจากคลาวด์และเก็บไว้ในแคชเพื่อใช้งานต่อไป สิ่งนี้จะเกิดขึ้นกับแต่ละภาพที่เลือก:
เมื่อให้ความสนใจกับผลลัพธ์ในคอนโซล คุณจะเห็นว่ารูปภาพใดถูกดาวน์โหลดจากคลาวด์ ซึ่งระบุด้วย FETCHED_IMAGE เนื่องจากนี่เป็นความพยายามครั้งแรกในการสร้างภาพต่อกัน ซึ่งหมายความว่าในขั้นตอนนี้ "แคชที่อ่อนแอ" ยังคงว่างเปล่า และรูปภาพทั้งหมดจะถูกดาวน์โหลดจากคลาวด์และใส่เข้าไป
แต่นอกจากกระบวนการดาวน์โหลดภาพแล้วยังมีกระบวนการล้างหน่วยความจำโดยตัวรวบรวมขยะอีกด้วย ซึ่งหมายความว่าวัตถุที่เก็บไว้ในแคชซึ่งเราอ้างถึงโดยใช้การอ้างอิงที่ไม่รัดกุมจะถูกลบโดยตัวรวบรวมขยะ และผู้เข้ารอบสุดท้ายของเราดำเนินการได้สำเร็จ โดยจะเป็นการลบคีย์ที่ใช้เก็บรูปภาพไว้ในแคช CLEANED_IMAGE แจ้งให้เราทราบเกี่ยวกับเรื่องนี้:
ต่อไป เราตระหนักว่าเราไม่ชอบภาพต่อกันที่เกิดขึ้น และตัดสินใจเปลี่ยนภาพใดภาพหนึ่งและสร้างภาพใหม่ ในการดำเนินการนี้ เพียงยกเลิกการเลือกรูปภาพที่ไม่จำเป็น เลือกรูปภาพอื่น แล้วคลิกปุ่ม "สร้างภาพต่อกัน" อีกครั้ง:
แต่คราวนี้ไม่ใช่รูปภาพทั้งหมดที่ถูกดาวน์โหลดจากเครือข่าย และหนึ่งในนั้นถูกนำมาจากแคชที่อ่อนแอ: ข้อความ CACHED_IMAGE บอกเราเกี่ยวกับมัน ซึ่งหมายความว่าในขณะที่สร้างภาพต่อกัน ตัวรวบรวมขยะยังไม่ได้ลบภาพของเรา และเรานำภาพนั้นออกจากแคชอย่างกล้าหาญ ซึ่งจะช่วยลดจำนวนคำขอเครือข่ายและเร่งเวลาโดยรวมของกระบวนการสร้างภาพต่อกัน:
มา "เล่น" กันอีกหน่อย โดยแทนที่รูปภาพใดรูปภาพหนึ่งอีกครั้งและสร้างภาพต่อกันใหม่:
คราวนี้ผลลัพธ์ที่ได้ก็น่าประทับใจยิ่งกว่าเดิม จาก 4 รูปภาพที่เลือก มี 3 ภาพถูกนำมาจากแคชที่อ่อนแอ และมีเพียงภาพเดียวเท่านั้นที่ต้องดาวน์โหลดจากเครือข่าย โหลดเครือข่ายลดลงประมาณ 75% น่าประทับใจใช่ไหม?
แน่นอนว่า สิ่งสำคัญคือต้องจำไว้ว่าพฤติกรรมดังกล่าวไม่ได้รับการรับประกัน และขึ้นอยู่กับการใช้งานและการดำเนินการเฉพาะของตัวรวบรวมขยะ
ด้วยเหตุนี้จึงเกิดคำถามเชิงตรรกะโดยสมบูรณ์ขึ้นมาทันที: ทำไมเราไม่ใช้แคชธรรมดาซึ่งเราสามารถจัดการเอนทิตีของมันเองได้แทนที่จะพึ่งพาตัวรวบรวมขยะ ถูกต้อง ในกรณีส่วนใหญ่ไม่จำเป็นต้องใช้ WeakRef
และ FinalizationRegistry
ในที่นี้ เราเพียงสาธิตการใช้งานทางเลือกอื่นของฟังก์ชันการทำงานที่คล้ายกัน โดยใช้แนวทางที่ไม่ซับซ้อนพร้อมฟีเจอร์ภาษาที่น่าสนใจ อย่างไรก็ตาม เราไม่สามารถพึ่งพาตัวอย่างนี้ได้ หากเราต้องการผลลัพธ์ที่คงที่และคาดเดาได้
คุณสามารถเปิดตัวอย่างนี้ได้ในแซนด์บ็อกซ์
WeakRef
– ออกแบบมาเพื่อสร้างการอ้างอิงที่ไม่รัดกุมไปยังออบเจ็กต์ ช่วยให้ตัวรวบรวมขยะสามารถลบออกจากหน่วยความจำได้ หากไม่มีการอ้างอิงที่รัดกุมอีกต่อไป สิ่งนี้มีประโยชน์สำหรับการจัดการการใช้หน่วยความจำที่มากเกินไปและการเพิ่มประสิทธิภาพการใช้ทรัพยากรระบบในแอปพลิเคชัน
FinalizationRegistry
– เป็นเครื่องมือสำหรับการลงทะเบียนการเรียกกลับซึ่งจะดำเนินการเมื่อวัตถุที่ไม่ได้อ้างอิงอย่างเข้มงวดอีกต่อไปถูกทำลาย ซึ่งจะช่วยให้สามารถปล่อยทรัพยากรที่เกี่ยวข้องกับวัตถุหรือดำเนินการที่จำเป็นอื่น ๆ ก่อนที่จะลบวัตถุออกจากหน่วยความจำ