ES6 Map และ WeakMap ต่างกันอย่างไร


94

เมื่อดูสิ่งนี้และหน้า MDN นี้ดูเหมือนว่าข้อแตกต่างเพียงอย่างเดียวระหว่าง Maps และ WeakMaps คือคุณสมบัติ "ขนาด" ที่ขาดหายไปสำหรับ WeakMaps แต่นี่เป็นเรื่องจริงหรือไม่? อะไรคือความแตกต่างระหว่างพวกเขา?


ผลกระทบต่อ GC WeakMaps สามารถรวบรวมคีย์ได้
John Dvorak

@JanDvorak ไม่มีตัวอย่างที่ชี้ไปที่ MDN เกี่ยวกับเรื่องนี้ เช่น aWeakMap.get (คีย์); // พูด 2 ... (การกระทำ GC) ... aWeakMap.get (คีย์); // พูดไม่ได้กำหนด
Dmitrii Sorin

1
ตัวอย่างของคุณเป็นไปไม่ได้ keyไม่สามารถรวบรวมได้เนื่องจากคุณอ้างถึง
John Dvorak

1
การตัดสินใจในการออกแบบคือการดำเนินการ GC จะมองไม่เห็นใน Javascript คุณไม่สามารถสังเกตเห็น GC ทำสิ่งนั้นได้
John Dvorak

1
ดูคำตอบที่เกี่ยวข้องสำหรับข้อมูลเพิ่มเติมเกี่ยวกับปัญหานี้
Benjamin Gruenbaum

คำตอบ:


54

จากหน้าเดียวกันส่วน " Why Weak Map? " :

โปรแกรมเมอร์ JavaScript ที่มีประสบการณ์จะสังเกตเห็นว่า API นี้สามารถใช้งานได้ใน JavaScript โดยมีสองอาร์เรย์ (อันหนึ่งสำหรับคีย์หนึ่งสำหรับค่า) ที่ใช้ร่วมกันโดย 4 เมธอด API การดำเนินการดังกล่าวจะมีความไม่สะดวกหลักสองประการ อันแรกคือการค้นหา O (n) (n เป็นจำนวนปุ่มในแผนที่) อย่างที่สองคือปัญหาหน่วยความจำรั่ว ด้วยแผนที่ที่เขียนด้วยตนเองอาร์เรย์ของคีย์จะเก็บข้อมูลอ้างอิงไปยังวัตถุสำคัญป้องกันไม่ให้ถูกเก็บรวบรวมขยะ ใน WeakMaps ดั้งเดิมการอ้างอิงถึงออบเจ็กต์สำคัญจะมีลักษณะ"อย่างอ่อน"ซึ่งหมายความว่าพวกเขาไม่ได้ป้องกันการรวบรวมขยะในกรณีที่ไม่มีการอ้างอิงอื่น ๆ ไปยังวัตถุ

เนื่องจากการอ้างอิงอ่อนแอคีย์ WeakMap จึงไม่สามารถนับได้ (กล่าวคือไม่มีวิธีใดที่ให้รายการคีย์แก่คุณ) หากเป็นเช่นนั้นรายการจะขึ้นอยู่กับสถานะของการเก็บขยะโดยนำเสนอสิ่งที่ไม่ใช่ปัจจัย

[และนั่นคือสาเหตุที่พวกเขาไม่มีsizeทรัพย์สินเช่นกัน]

หากคุณต้องการมีรายการคีย์คุณควรดูแลรักษาด้วยตัวเอง นอกจากนี้ยังมีข้อเสนอ ECMAScript ซึ่ง มีจุดมุ่งหมายเพื่อแนะนำชุดและแผนที่ง่ายๆซึ่งจะไม่ใช้การอ้างอิงที่ไม่รัดกุมและสามารถแจกแจงได้

- ซึ่งจะเป็น"ปกติ" s ไม่ได้กล่าวถึงที่ MDN แต่ในข้อเสนอสามัคคีเหล่านั้นยังมี, และวิธีการกำเนิดและใช้อินเตอร์เฟซMapitemskeysvaluesIterator


ดังนั้นจึงnew Map().get(x)มีเวลาในการค้นหาเช่นเดียวกับการอ่านคุณสมบัติจากวัตถุธรรมดาหรือไม่?
Alexander Mills

1
@AlexanderMills ฉันไม่เห็นว่าสิ่งนี้จะทำอย่างไรกับคำถาม แต่นี่คือข้อมูลบางอย่าง โดยทั่วไปใช่พวกเขามีความคล้ายคลึงและคุณควรใช้ที่เหมาะสมหนึ่ง
Bergi

ดังนั้นความเข้าใจของฉันคือ Map รักษาอาร์เรย์ภายในเพื่อคงคีย์ไว้เนื่องจากอาร์เรย์นั้น คนเก็บขยะไม่สามารถที่จะละเว้นการอ้างอิง ใน WeekMap จะไม่มีอาร์เรย์ที่เก็บคีย์ไว้ดังนั้นคีย์ที่ไม่มีการอ้างอิงจึงสามารถเก็บขยะได้
Mohan Ram

@MohanRam A WeakMapยังคงมีอาร์เรย์ (หรือคอลเลกชันอื่น ๆ ) ของรายการมันเพียงแค่บอกคนเก็บขยะว่าสิ่งเหล่านั้นอ้างอิงไม่ดี
Bergi

แล้วทำไมการทำซ้ำสำหรับคีย์ WeekMap จึงไม่รองรับ?
Mohan Ram

93

ทั้งสองทำงานแตกต่างกันเมื่อวัตถุที่อ้างอิงโดยคีย์ / ค่าของพวกเขาถูกลบ ให้ใช้โค้ดตัวอย่างด้านล่าง:

var map = new Map();
var weakmap = new WeakMap();

(function(){
    var a = {x: 12};
    var b = {y: 12};

    map.set(a, 1);
    weakmap.set(b, 2);
})()

IIFE ดังกล่าวข้างต้นจะถูกดำเนินการมีวิธีที่เราสามารถอ้างอิงไม่มี{x: 12}และ{y: 12}อีกต่อไป ตัวเก็บขยะจะดำเนินการต่อและลบตัวชี้คีย์ b จาก“ WeakMap” และลบออก{y: 12}จากหน่วยความจำด้วย แต่ในกรณีของ“ แผนที่” ตัวเก็บขยะจะไม่ลบตัวชี้ออกจาก“ แผนที่” และไม่ได้ลบออก{x: 12}จากหน่วยความจำด้วย

สรุป: WeakMap อนุญาตให้ตัวเก็บขยะทำงานได้ แต่ไม่ใช่แผนที่

ข้อมูลอ้างอิง: http://qnimate.com/difference-between-map-and-weakmap-in-javascript/


13
ทำไมถึงไม่ถูกลบออกจากความทรงจำ? เพราะยังอ้างอิงได้! map.entries().next().value // [{x:12}, 1]
Bergi

4
ไม่ใช่ฟังก์ชันที่เรียกใช้เอง มันเป็นนิพจน์ฟังก์ชันที่เรียกใช้ทันที benalman.com/news/2010/11/…
Olson.dev

แล้วความแตกต่างระหว่างจุดอ่อนกับวัตถุคืออะไร
มูฮัมหมัด Umer

@MuhammadUmer: ออบเจ็กต์สามารถมีสตริง 'คีย์' เท่านั้นในขณะที่WeakMapสามารถมีคีย์ที่ไม่ใช่แบบดั้งเดิมเท่านั้น (ไม่มีสตริงหรือตัวเลขหรือSymbols เป็นคีย์เฉพาะอาร์เรย์อ็อบเจ็กต์แผนที่อื่น ๆ ฯลฯ )
Ahmed Fasih

1
@nnnnnn ใช่นั่นคือความแตกต่างมันยังคงอยู่Map แต่ไม่ใช่ในWeakMap
Alexander Derck

76

บางทีคำอธิบายต่อไปอาจชัดเจนสำหรับใครบางคน

var k1 = {a: 1};
var k2 = {b: 2};

var map = new Map();
var wm = new WeakMap();

map.set(k1, 'k1');
wm.set(k2, 'k2');

k1 = null;
map.forEach(function (val, key) {
    console.log(key, val); // k1 {a: 1}
});

k2 = null;
wm.get(k2); // undefined

อย่างที่คุณเห็นหลังจากลบk1คีย์ออกจากหน่วยความจำแล้วเรายังสามารถเข้าถึงมันได้ภายในแผนที่ ในขณะเดียวกันก็ลบk2คีย์ของ WeakMap ออกwmด้วยเช่นกันโดยการอ้างอิง

นั่นเป็นเหตุผลที่ WeakMap ไม่มีวิธีการแจกแจงเช่น forEach เนื่องจากไม่มีรายการคีย์ WeakMap เป็นเพียงการอ้างอิงไปยังวัตถุอื่น


10
ในบรรทัดสุดท้ายแน่นอนว่า wm.get (null) จะไม่ถูกกำหนด
DaNeSh

8
คำตอบที่ดีกว่าการคัดลอกและวางจากเว็บไซต์ mozilla, kudos
Joel Hernandez

2
ในforEach, (key, val)ควรจะเป็นจริง(val, key)
มิเกลโมตะ

ไม่น่าเชื่อว่าตัวอย่างที่ไม่สมเหตุสมผลได้รับ
คะแนนโหวต

34

ความแตกต่างอีกประการหนึ่ง (ที่มา: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap ):

คีย์ของ WeakMaps เป็นประเภท Object เท่านั้น ไม่อนุญาตให้ใช้ประเภทข้อมูลดั้งเดิมเป็นคีย์ (เช่น Symbol ไม่สามารถเป็นคีย์ WeakMap ได้)

ไม่สามารถใช้สตริงตัวเลขหรือบูลีนเป็นWeakMapคีย์ได้ A Map สามารถใช้ค่าดั้งเดิมสำหรับคีย์

w = new WeakMap;
w.set('a', 'b'); // Uncaught TypeError: Invalid value used as weak map key

m = new Map
m.set('a', 'b'); // Works

6
ในกรณีที่มีใครสงสัย: ฉันนึกออกว่าเหตุผลเบื้องหลังคือคุณไม่สามารถเก็บหรือส่งต่อการอ้างอิงถึงประเภทดั้งเดิม ดังนั้นคีย์ใน WeakMap จะเป็นเพียงข้อมูลอ้างอิงเท่านั้น การเก็บขยะแบบนั้นคงเป็นไปไม่ได้ ฉันไม่รู้ว่าการอ้างอิงที่อ่อนแอนั้นเป็นไปไม่ได้หรือไม่สมเหตุสมผล แต่ไม่ว่าจะด้วยวิธีใดคีย์จะต้องเป็นสิ่งที่สามารถอ้างอิงได้อย่างอ่อน
Andreas Linnert

3

จากJavascript.info

แผนที่ - หากเราใช้วัตถุเป็นกุญแจสำคัญในแผนที่ปกติในขณะที่มีแผนที่วัตถุนั้นก็มีอยู่เช่นกัน มันใช้หน่วยความจำและอาจไม่เก็บขยะ

let john = { name: "John" };
let array = [ john ];
john = null; // overwrite the reference

// john is stored inside the array, so it won't be garbage-collected
// we can get it as array[0]

ในทำนองเดียวกันถ้าเราใช้วัตถุเป็นกุญแจสำคัญในแผนที่ปกติในขณะที่มีแผนที่วัตถุนั้นก็มีอยู่เช่นกัน มันใช้หน่วยความจำและอาจไม่เก็บขยะ

let john = { name: "John" };
let map = new Map();
map.set(john, "...");
john = null; // overwrite the reference

// john is stored inside the map,
// we can get it by using map.keys()

WeakMap - ตอนนี้ถ้าเราใช้วัตถุเป็นกุญแจสำคัญในนั้นและไม่มีการอ้างอิงอื่น ๆ ไปยังวัตถุนั้น - มันจะถูกลบออกจากหน่วยความจำ (และจากแผนที่) โดยอัตโนมัติ

let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // overwrite the reference

// john is removed from memory!

3

WeapMap ใน javascript ไม่ได้เก็บคีย์หรือค่าใด ๆ เพียงแค่ปรับแต่งค่าคีย์โดยใช้id เฉพาะและกำหนดคุณสมบัติให้กับคีย์

เพราะมันกำหนดคุณสมบัติการkey objectโดยวิธีการObject.definePropert(), ที่สำคัญจะต้องไม่เป็นชนิดดั้งเดิม

และเนื่องจาก WeapMap ไม่มีคู่ค่าคีย์จริงเราจึงไม่สามารถรับคุณสมบัติความยาวของจุดอ่อนแผนที่ได้

และยังมีการกำหนดค่าที่ถูกจัดการกลับไปยังวัตถุหลักตัวเก็บขยะสามารถรวบรวมคีย์ได้อย่างง่ายดายหากไม่มีการใช้งาน

โค้ดตัวอย่างสำหรับการนำไปใช้งาน

if(typeof WeapMap != undefined){
return;
} 
(function(){
   var WeapMap = function(){
      this.__id = '__weakmap__';
   }
        
   weakmap.set = function(key,value){
       var pVal = key[this.__id];
        if(pVal && pVal[0] == key){
           pVal[1]=value;
       }else{
          Object.defineProperty(key, this.__id, {value:[key,value]});
          return this;
        }
   }

window.WeakMap = WeakMap;
})();

การอ้างอิงการใช้งาน


1
เพื่อความชัดเจนการใช้งานนี้ได้ผลเพียงครึ่งเดียว ไม่อนุญาตให้ใช้วัตถุเดียวกันกับคีย์ในแผนที่ที่อ่อนแอหลายรายการ นอกจากนี้ยังใช้ไม่ได้กับวัตถุที่แช่แข็ง และแน่นอนว่าการทำแผนที่รั่วไหลไปสู่ใครก็ตามที่มีการอ้างอิงถึงวัตถุ ครั้งแรกสามารถแก้ไขได้โดยใช้สัญลักษณ์ แต่ไม่ใช่สองตัวหลัง
Andreas Rossberg

@AndreasRossberg ในการใช้งานนี้ฉันได้เพิ่มฮาร์ดโค้ดidแต่สิ่งนี้ควรจะไม่ซ้ำกันโดยใช้บางอย่าง Math.random และ Date.now () เป็นต้นและด้วยการเพิ่ม ID ไดนามิกนี้จุดแรกสามารถแก้ไขได้ คุณช่วยกรุณาให้วิธีแก้ปัญหาสำหรับสองจุดสุดท้าย
Ravi Sevta

ปัญหาแรกได้รับการแก้ไขอย่างหรูหรายิ่งขึ้นโดยใช้สัญลักษณ์ สองข้อหลังไม่สามารถแก้ไขได้ภายใน JS ซึ่งเป็นสาเหตุที่ WeakMap ต้องเป็นภาษาดั้งเดิม
Andreas Rossberg

1

WeakMap คีย์ต้องเป็นวัตถุไม่ใช่ค่าดั้งเดิม

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", "Not ok"); // Error, because "test" is not an object

ทำไม????

ลองดูตัวอย่างด้านล่าง

let user = { name: "User" };

let map = new Map();
map.set(user, "...");

user = null; // overwrite the reference

// 'user' is stored inside the map,
// We can get it by using map.keys()

ถ้าเราใช้ออบเจ็กต์เป็นคีย์ในปกติMapแล้วในขณะที่ Mapมีอยู่อ็อบเจ็กต์นั้นก็มีอยู่เช่นกัน มันใช้หน่วยความจำและอาจไม่เก็บขยะ

WeakMapมีความแตกต่างกันโดยพื้นฐานในด้านนี้ ไม่ได้ป้องกันการเก็บขยะของวัตถุสำคัญ

let user = { name: "User" };

let weakMap = new WeakMap();
weakMap.set(user, "...");

user = null; // overwrite the reference

// 'user' is removed from memory!

หากเราใช้วัตถุเป็นกุญแจสำคัญในนั้นและไม่มีการอ้างอิงอื่น ๆ ไปยังวัตถุนั้น - สิ่งนั้นจะถูกลบออกจากหน่วยความจำ (และจากแผนที่) โดยอัตโนมัติ

WeakMap ไม่รองรับการทำซ้ำและคีย์วิธีการ() , ค่า () , รายการ ()ดังนั้นจึงไม่มีวิธีรับคีย์หรือค่าทั้งหมดจากคีย์นั้น

WeakMap มีเพียงวิธีการดังต่อไปนี้:

  • อ่อนแอMap.get (คีย์)
  • อ่อนแอMap.set (คีย์ค่า)
  • อ่อนแอMap.delete (คีย์)
  • อ่อนแอMap.has (คีย์)

เห็นได้ชัดว่าหากวัตถุสูญเสียการอ้างอิงอื่น ๆ ทั้งหมด (เช่น 'ผู้ใช้' ในโค้ดด้านบน) จากนั้นจะถูกรวบรวมโดยอัตโนมัติ แต่ในทางเทคนิคไม่ได้ระบุไว้อย่างแน่นอนเมื่อการล้างข้อมูลเกิดขึ้น

เอ็นจิ้น JavaScript ตัดสินใจว่า อาจเลือกที่จะดำเนินการล้างหน่วยความจำทันทีหรือรอและทำความสะอาดในภายหลังเมื่อมีการลบเกิดขึ้นมากขึ้น ดังนั้นในทางเทคนิคWeakMapจึงไม่ทราบจำนวนองค์ประกอบปัจจุบันของ a เครื่องยนต์อาจทำความสะอาดหมดแล้วหรือไม่หรือทำเพียงบางส่วน ด้วยเหตุนี้จึงไม่รองรับวิธีการเข้าถึงคีย์ / ค่าทั้งหมด

หมายเหตุ: -พื้นที่หลักของแอปพลิเคชันสำหรับ WeakMap คือพื้นที่จัดเก็บข้อมูลเพิ่มเติม เช่นเดียวกับการแคชวัตถุจนกว่าวัตถุนั้นจะได้รับขยะ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.