ดังที่เราทราบจากบท Garbage Collection เอ็นจิ้น JavaScript จะเก็บค่าไว้ในหน่วยความจำในขณะที่ "เข้าถึงได้" และอาจนำไปใช้ได้
ตัวอย่างเช่น:
ให้จอห์น = { ชื่อ: "จอห์น" }; // สามารถเข้าถึงวัตถุได้ john คือการอ้างอิงถึงวัตถุนั้น // เขียนทับข้อมูลอ้างอิง จอห์น = โมฆะ; // วัตถุจะถูกลบออกจากหน่วยความจำ
โดยปกติแล้ว คุณสมบัติของวัตถุหรือองค์ประกอบของอาร์เรย์หรือโครงสร้างข้อมูลอื่นจะถือว่าสามารถเข้าถึงได้และเก็บไว้ในหน่วยความจำในขณะที่โครงสร้างข้อมูลนั้นอยู่ในหน่วยความจำ
ตัวอย่างเช่น ถ้าเราใส่วัตถุเข้าไปในอาร์เรย์ ในขณะที่อาร์เรย์ยังมีชีวิตอยู่ วัตถุก็จะมีชีวิตอยู่เช่นกัน แม้ว่าจะไม่มีการอ้างอิงอื่นใดถึงมันก็ตาม
แบบนี้:
ให้จอห์น = { ชื่อ: "จอห์น" }; ให้ array = [ จอห์น ]; จอห์น = โมฆะ; // เขียนทับข้อมูลอ้างอิง // วัตถุที่ john อ้างอิงก่อนหน้านี้ถูกเก็บไว้ในอาร์เรย์ // จึงไม่เก็บขยะ // เราสามารถรับมันเป็นอาร์เรย์[0]
ในทำนองเดียวกัน หากเราใช้วัตถุเป็นกุญแจใน Map
ปกติ ในขณะที่ Map
มีอยู่ วัตถุนั้นก็จะมีอยู่เช่นกัน มันใช้หน่วยความจำและอาจไม่ถูกรวบรวมขยะ
ตัวอย่างเช่น:
ให้จอห์น = { ชื่อ: "จอห์น" }; ให้ map = new Map(); map.set(จอห์น, "..."); จอห์น = โมฆะ; // เขียนทับข้อมูลอ้างอิง // จอห์นถูกเก็บไว้ในแผนที่ // เราหามันได้โดยใช้ map.keys()
WeakMap
มีความแตกต่างโดยพื้นฐานในด้านนี้ มันไม่ได้ป้องกันการรวบรวมขยะของวัตถุสำคัญ
เรามาดูกันว่ามันหมายถึงอะไรในตัวอย่าง
ข้อแตกต่างแรกระหว่าง Map
และ WeakMap
คือคีย์จะต้องเป็นวัตถุ ไม่ใช่ค่าดั้งเดิม:
ให้อ่อนแอMap = ใหม่ WeakMap(); ให้ obj = {}; อ่อนแอMap.set(obj, "ตกลง"); // ทำงานได้ดี (คีย์วัตถุ) // ไม่สามารถใช้สตริงเป็นคีย์ได้ อ่อนแอMap.set("ทดสอบ", "อ๊ะ"); // เกิดข้อผิดพลาด เนื่องจาก "test" ไม่ใช่วัตถุ
ตอนนี้ หากเราใช้วัตถุเป็นกุญแจในนั้น และไม่มีการอ้างอิงอื่นไปยังวัตถุนั้น - มันจะถูกลบออกจากหน่วยความจำ (และจากแผนที่) โดยอัตโนมัติ
ให้จอห์น = { ชื่อ: "จอห์น" }; ให้อ่อนแอMap = ใหม่ WeakMap(); อ่อนแอMap.set(จอห์น, "..."); จอห์น = โมฆะ; // เขียนทับข้อมูลอ้างอิง // จอห์นถูกลบออกจากหน่วยความจำ!
เปรียบเทียบกับตัวอย่าง Map
ปกติด้านบน ตอนนี้ถ้า john
มีอยู่เป็นคีย์ของ WeakMap
เท่านั้น มันจะถูกลบออกจากแผนที่โดยอัตโนมัติ (และหน่วยความจำ)
WeakMap
ไม่รองรับการวนซ้ำและวิธีการ keys()
, values()
, entries()
ดังนั้นจึงไม่มีวิธีรับคีย์หรือค่าทั้งหมดจากมัน
WeakMap
มีวิธีการดังต่อไปนี้เท่านั้น:
weakMap.set(key, value)
weakMap.get(key)
weakMap.delete(key)
weakMap.has(key)
เหตุใดจึงมีข้อจำกัดเช่นนี้? นั่นเป็นเหตุผลทางเทคนิค หากวัตถุสูญเสียการอ้างอิงอื่นๆ ทั้งหมด (เช่น john
ในโค้ดด้านบน) วัตถุนั้นก็จะถูกรวบรวมโดยอัตโนมัติ แต่ในทางเทคนิคแล้ว ไม่มีการระบุไว้อย่างชัดเจน ว่าการล้างข้อมูลจะเกิดขึ้นเมื่อใด
เอ็นจิ้น JavaScript ตัดสินใจเช่นนั้น อาจเลือกที่จะดำเนินการล้างหน่วยความจำทันทีหรือรอและทำความสะอาดในภายหลังเมื่อมีการลบเพิ่มเติม ดังนั้น ในทางเทคนิคแล้ว ไม่ทราบจำนวนองค์ประกอบปัจจุบันของ WeakMap
เครื่องยนต์อาจจะทำความสะอาดไปแล้วหรือบางส่วนก็ได้ ด้วยเหตุผลดังกล่าว จึงไม่รองรับวิธีการที่เข้าถึงคีย์/ค่าทั้งหมด
ตอนนี้เราต้องการโครงสร้างข้อมูลดังกล่าวที่ไหน?
พื้นที่หลักของแอปพลิเคชันสำหรับ WeakMap
คือ การจัดเก็บข้อมูลเพิ่มเติม
หากเรากำลังทำงานกับอ็อบเจ็กต์ที่ "เป็น" ของโค้ดอื่น อาจเป็นไลบรารีของบุคคลที่สามด้วยซ้ำ และต้องการจัดเก็บข้อมูลบางส่วนที่เกี่ยวข้องกับโค้ดนั้น ซึ่งควรจะมีอยู่ในขณะที่วัตถุนั้นยังมีชีวิตอยู่เท่านั้น ดังนั้น WeakMap
ก็คือสิ่งที่เป็น จำเป็น
เราใส่ข้อมูลลงใน WeakMap
โดยใช้อ็อบเจ็กต์เป็นคีย์ และเมื่ออ็อบเจ็กต์ถูกรวบรวมแบบขยะ ข้อมูลนั้นก็จะหายไปโดยอัตโนมัติเช่นกัน
อ่อนแอMap.set(john, "เอกสารลับ"); // ถ้าจอห์นตาย เอกสารลับจะถูกทำลายโดยอัตโนมัติ
ลองดูตัวอย่าง
ตัวอย่างเช่น เรามีโค้ดที่เก็บจำนวนการเข้าชมของผู้ใช้ ข้อมูลจะถูกจัดเก็บไว้ในแผนที่: วัตถุผู้ใช้คือกุญแจ และจำนวนการเยี่ยมชมคือมูลค่า เมื่อผู้ใช้ออกไป (วัตถุถูกเก็บขยะ) เราไม่ต้องการบันทึกจำนวนการเข้าชมอีกต่อไป
นี่คือตัวอย่างฟังก์ชันการนับด้วย Map
:
- visitsCount.js ให้ visitsCountMap = new Map(); // map: user => จำนวนการเข้าชม // เพิ่มจำนวนการเข้าชม ฟังก์ชั่น countUser (ผู้ใช้) { ให้นับ = visitsCountMap.get (ผู้ใช้) || 0; visitsCountMap.set(ผู้ใช้, นับ + 1); -
และนี่คืออีกส่วนหนึ่งของโค้ด อาจเป็นไฟล์อื่นที่ใช้โค้ดนี้:
- main.js ให้จอห์น = { ชื่อ: "จอห์น" }; countUser(จอห์น); // นับการมาเยือนของเขา //ต่อมาจอห์นก็จากเราไป จอห์น = โมฆะ;
ตอนนี้ john
object ควรถูกรวบรวมแบบขยะ แต่ยังคงอยู่ในหน่วยความจำ เนื่องจากเป็นกุญแจสำคัญใน visitsCountMap
เราจำเป็นต้องทำความสะอาด visitsCountMap
เมื่อเราลบผู้ใช้ ไม่เช่นนั้นมันจะขยายใหญ่ขึ้นในหน่วยความจำอย่างไม่มีกำหนด การทำความสะอาดดังกล่าวอาจกลายเป็นงานที่น่าเบื่อในสถาปัตยกรรมที่ซับซ้อน
เราสามารถหลีกเลี่ยงได้โดยเปลี่ยนไปใช้ WeakMap
แทน:
- visitsCount.js ให้ visitsCountMap = new WeakMap(); //weakmap: user => จำนวนการเข้าชม // เพิ่มจำนวนการเข้าชม ฟังก์ชั่น countUser (ผู้ใช้) { ให้นับ = visitsCountMap.get (ผู้ใช้) || 0; visitsCountMap.set(ผู้ใช้, นับ + 1); -
ตอนนี้เราไม่จำเป็นต้องล้างข้อมูล visitsCountMap
หลังจากที่วัตถุ john
ไม่สามารถเข้าถึงได้ ยกเว้นในกรณีที่เป็นคีย์ของ WeakMap
มันจะถูกลบออกจากหน่วยความจำ พร้อมกับข้อมูลด้วยคีย์นั้นจาก WeakMap
อีกตัวอย่างทั่วไปคือการแคช เราสามารถจัดเก็บผลลัพธ์ (“แคช”) จากฟังก์ชันได้ เพื่อให้การเรียกใช้ออบเจ็กต์เดียวกันในอนาคตสามารถนำกลับมาใช้ใหม่ได้
เพื่อให้บรรลุเป้าหมายดังกล่าว เราสามารถใช้ Map
(ไม่ใช่สถานการณ์ที่เหมาะสมที่สุด):
- cache.js ให้แคช = แผนที่ใหม่ (); // คำนวณและจดจำผลลัพธ์ กระบวนการทำงาน (obj) { ถ้า (!cache.has(obj)) { ให้ผลลัพธ์ = /* การคำนวณผลลัพธ์สำหรับ */ obj; cache.set(obj ผลลัพธ์); ส่งคืนผลลัพธ์; - กลับ cache.get(obj); - // ตอนนี้เราใช้ process() ในไฟล์อื่น: - main.js ให้ obj = {/* สมมติว่าเรามีวัตถุ */}; ให้ result1 = กระบวนการ (obj); //คำนวณแล้ว // ...ต่อมา จากที่อื่นของโค้ด... ให้ result2 = กระบวนการ (obj); // จำผลลัพธ์ที่นำมาจากแคช // ...ต่อมา เมื่อไม่ต้องการวัตถุอีกต่อไป: obj = โมฆะ; การแจ้งเตือน (แคชขนาด); // 1 (อุ๊ย! วัตถุยังอยู่ในแคช กำลังใช้หน่วยความจำ!)
สำหรับการเรียก process(obj)
หลายครั้งด้วยวัตถุเดียวกัน มันจะคำนวณผลลัพธ์ในครั้งแรกเท่านั้น จากนั้นจึงนำมาจาก cache
ข้อเสียคือเราต้องล้าง cache
เมื่อไม่ต้องการวัตถุอีกต่อไป
หากเราแทนที่ Map
ด้วย WeakMap
ปัญหานี้จะหายไป ผลลัพธ์ที่แคชจะถูกลบออกจากหน่วยความจำโดยอัตโนมัติหลังจากที่วัตถุได้รับการรวบรวมขยะ
- cache.js ให้แคช = ใหม่ WeakMap(); // คำนวณและจดจำผลลัพธ์ กระบวนการทำงาน (obj) { ถ้า (!cache.has(obj)) { ให้ result = /* คำนวณผลลัพธ์สำหรับ */ obj; cache.set(obj ผลลัพธ์); ส่งคืนผลลัพธ์; - กลับ cache.get(obj); - - main.js ให้ obj = {/* วัตถุบางอย่าง */}; ให้ result1 = กระบวนการ (obj); ให้ result2 = กระบวนการ (obj); // ...ต่อมา เมื่อไม่ต้องการวัตถุอีกต่อไป: obj = โมฆะ; // ไม่สามารถรับ cache.size ได้ เนื่องจากเป็น WeakMap // แต่มันเป็น 0 หรือเร็ว ๆ นี้จะเป็น 0 // เมื่อ obj ได้รับการรวบรวมขยะ ข้อมูลแคชจะถูกลบออกเช่นกัน
WeakSet
มีพฤติกรรมคล้ายกัน:
มันคล้ายคลึงกับ Set
แต่เราอาจเพิ่มวัตถุลงใน WeakSet
เท่านั้น (ไม่ใช่แบบดั้งเดิม)
มีวัตถุอยู่ในชุดขณะที่สามารถเข้าถึงได้จากที่อื่น
เช่นเดียวกับ Set
มันรองรับ add
, has
และ delete
แต่ไม่รองรับ size
, keys()
และไม่มีการวนซ้ำ
เนื่องจากมีความ “อ่อนแอ” จึงยังทำหน้าที่เป็นพื้นที่จัดเก็บเพิ่มเติมอีกด้วย แต่ไม่ใช่เพื่อข้อมูลตามอำเภอใจ แต่เพื่อข้อเท็จจริงที่ "ใช่/ไม่ใช่" การเป็นสมาชิกใน WeakSet
อาจหมายถึงบางสิ่งเกี่ยวกับวัตถุ
ตัวอย่างเช่น เราสามารถเพิ่มผู้ใช้ลงใน WeakSet
เพื่อติดตามผู้ที่เยี่ยมชมเว็บไซต์ของเรา:
ให้ visitSet = new WeakSet(); ให้จอห์น = { ชื่อ: "จอห์น" }; ให้พีท = { ชื่อ: "พีท" }; ให้แมรี่ = { ชื่อ: "แมรี่" }; เยี่ยมชม Set.add (จอห์น); // จอห์นมาเยี่ยมพวกเรา visitSet.add(พีท); //แล้วพีท เยี่ยมชม Set.add (จอห์น); //จอห์นอีกแล้ว // visitSet มีผู้ใช้ 2 คนแล้ว // ตรวจสอบว่าจอห์นมาเยี่ยมไหม? การแจ้งเตือน (เยี่ยมชมSet.has (john)); // จริง // เช็คดูว่าแมรี่มาเยี่ยมมั้ย? การแจ้งเตือน (visitedSet.has (แมรี่)); // เท็จ จอห์น = โมฆะ; // visitSet จะถูกล้างโดยอัตโนมัติ
ข้อจำกัดที่โดดเด่นที่สุดของ WeakMap
และ WeakSet
คือการไม่มีการวนซ้ำ และไม่สามารถรับเนื้อหาปัจจุบันทั้งหมดได้ ซึ่งอาจดูเหมือนไม่สะดวก แต่ไม่ได้ป้องกัน WeakMap/WeakSet
จากการทำงานหลัก - เป็นที่จัดเก็บข้อมูล "เพิ่มเติม" สำหรับออบเจ็กต์ที่จัดเก็บ/จัดการที่อื่น
WeakMap
เป็นคอลเลกชันที่มีลักษณะคล้าย Map
ที่อนุญาตให้เฉพาะวัตถุเป็นกุญแจ และลบออกพร้อมกับค่าที่เกี่ยวข้องเมื่อไม่สามารถเข้าถึงได้ด้วยวิธีอื่น
WeakSet
เป็นคอลเลกชันที่มีลักษณะคล้าย Set
ที่จะจัดเก็บเฉพาะวัตถุและลบออกเมื่อไม่สามารถเข้าถึงได้ด้วยวิธีอื่น
ข้อได้เปรียบหลักคือมีการอ้างอิงถึงอ็อบเจ็กต์ได้ไม่ดี ดังนั้นตัวรวบรวมขยะจึงสามารถกำจัดออกได้อย่างง่ายดาย
นั่นมาพร้อมกับต้นทุนของการไม่รองรับ clear
size
keys
values
...
WeakMap
และ WeakSet
ถูกใช้เป็นโครงสร้างข้อมูล "รอง" นอกเหนือจากที่เก็บข้อมูลออบเจ็กต์ "หลัก" เมื่อวัตถุถูกลบออกจากที่เก็บข้อมูลหลัก หากพบว่าเป็นคีย์ของ WeakMap
หรือใน WeakSet
เท่านั้น วัตถุนั้นจะถูกล้างโดยอัตโนมัติ
ความสำคัญ: 5
มีข้อความมากมาย:
ให้ข้อความ = [ {ข้อความ: "สวัสดี" จาก: "จอห์น"} {text: "เป็นยังไงบ้าง", จาก: "จอห์น"}, {text: "แล้วพบกันใหม่" จาก: "อลิซ"} -
รหัสของคุณสามารถเข้าถึงได้ แต่ข้อความได้รับการจัดการโดยรหัสของบุคคลอื่น มีการเพิ่มข้อความใหม่ ข้อความเก่าจะถูกลบออกเป็นประจำด้วยโค้ดนั้น และคุณไม่ทราบช่วงเวลาที่แน่ชัดว่าจะเกิดขึ้นเมื่อใด
ทีนี้ คุณสามารถใช้โครงสร้างข้อมูลใดในการจัดเก็บข้อมูลว่าข้อความ "ถูกอ่านแล้ว" หรือไม่ โครงสร้างต้องเหมาะสมอย่างยิ่งจึงจะตอบได้ว่า “อ่านแล้ว?” สำหรับวัตถุข้อความที่กำหนด
ป.ล. เมื่อข้อความถูกลบออกจาก messages
นั้นก็ควรจะหายไปจากโครงสร้างของคุณด้วย
PPS เราไม่ควรแก้ไขวัตถุข้อความ เพิ่มคุณสมบัติของเราลงไป เนื่องจากได้รับการจัดการโดยรหัสของผู้อื่น จึงอาจทำให้เกิดผลเสียตามมาได้
มาเก็บอ่านข้อความใน WeakSet
:
ให้ข้อความ = [ {ข้อความ: "สวัสดี" จาก: "จอห์น"} {text: "เป็นยังไงบ้าง", จาก: "จอห์น"}, {text: "แล้วพบกันใหม่" จาก: "อลิซ"} - ให้ readMessages = new WeakSet(); // มีการอ่านข้อความสองข้อความแล้ว readMessages.add(ข้อความ[0]); readMessages.add(ข้อความ[1]); // readMessages มี 2 องค์ประกอบ // ...มาอ่านข้อความแรกกันใหม่อีกครั้ง! readMessages.add(ข้อความ[0]); // readMessages ยังคงมี 2 องค์ประกอบที่ไม่ซ้ำใคร // คำตอบ: ข้อความ [0] อ่านแล้วหรือยัง? alert("อ่านข้อความ 0: " + readMessages.has(ข้อความ[0])); // จริง ข้อความ.กะ(); // ตอนนี้ readMessages มี 1 องค์ประกอบ (ทางเทคนิคแล้วหน่วยความจำอาจถูกล้างในภายหลัง)
WeakSet
ช่วยให้สามารถจัดเก็บชุดข้อความและตรวจสอบการมีอยู่ของข้อความได้อย่างง่ายดาย
มันจะทำความสะอาดตัวเองโดยอัตโนมัติ ข้อเสียคือเราไม่สามารถทำซ้ำได้ และไม่สามารถรับ “ข้อความที่อ่านแล้วทั้งหมด” จากข้อความนั้นได้โดยตรง แต่เราสามารถทำได้โดยวนซ้ำข้อความทั้งหมดและกรองข้อความที่อยู่ในชุด
วิธีแก้ปัญหาอื่นที่แตกต่างออกไปคือการเพิ่มคุณสมบัติเช่น message.isRead=true
ให้กับข้อความหลังจากที่อ่านแล้ว เนื่องจากออบเจ็กต์ข้อความได้รับการจัดการโดยโค้ดอื่น โดยทั่วไปจึงไม่สนับสนุน แต่เราสามารถใช้คุณสมบัติเชิงสัญลักษณ์เพื่อหลีกเลี่ยงความขัดแย้งได้
แบบนี้:
// คุณสมบัติเชิงสัญลักษณ์เป็นที่รู้จักในโค้ดของเราเท่านั้น ให้ isRead = Symbol("isRead"); ข้อความ [0] [isRead] = จริง;
ตอนนี้โค้ดของบุคคลที่สามอาจจะไม่เห็นคุณสมบัติพิเศษของเรา
แม้ว่าสัญลักษณ์จะช่วยลดความน่าจะเป็นของปัญหาได้ แต่การใช้ WeakSet
จะดีกว่าจากมุมมองทางสถาปัตยกรรม
ความสำคัญ: 5
มีข้อความมากมายเหมือนงานก่อนหน้า สถานการณ์คล้ายกัน
ให้ข้อความ = [ {ข้อความ: "สวัสดี" จาก: "จอห์น"} {text: "เป็นยังไงบ้าง", จาก: "จอห์น"}, {text: "แล้วพบกันใหม่" จาก: "อลิซ"} -
คำถามตอนนี้คือ: โครงสร้างข้อมูลใดที่คุณแนะนำให้จัดเก็บข้อมูล: “ข้อความถูกอ่านเมื่อใด”
ในงานก่อนหน้านี้ เราจำเป็นต้องจัดเก็บข้อเท็จจริง "ใช่/ไม่ใช่" เท่านั้น ตอนนี้เราจำเป็นต้องจัดเก็บวันที่ และควรคงอยู่ในหน่วยความจำจนกว่าข้อความจะถูกรวบรวมแบบขยะ
PS Dates สามารถจัดเก็บเป็นออบเจ็กต์ของคลาส Date
ในตัวได้ ซึ่งเราจะกล่าวถึงในภายหลัง
หากต้องการจัดเก็บวันที่ เราสามารถใช้ WeakMap
:
ให้ข้อความ = [ {ข้อความ: "สวัสดี" จาก: "จอห์น"} {text: "เป็นยังไงบ้าง", จาก: "จอห์น"}, {text: "แล้วพบกันใหม่" จาก: "อลิซ"} - ให้ readMap = ใหม่ WeakMap(); readMap.set (ข้อความ [0], วันที่ใหม่ (2017, 1, 1)); // วัตถุวันที่เราจะศึกษาในภายหลัง