The original intention of this paper is the precipitation and summary of the two data types, Map and Set, and their related types. Hopefully, it will also be an introduction to others.

The index

  • Map
  • WeakMap
  • Set
  • WeakSet
  • reference

Map

define

  • The new collection type truly implements the storage mechanism in the form of key-value pairs.

Basic API

Initialize the

  • Unlike objects, which can be created using Object literals, maps are created using constructors.

  • The arguments passed must be iterable objects.

    // No parameter
    const m1 = new Map(a);// Iterable parameters
    const m2 = new Map([['key1'.'value1'],
      ['key2'.'value2']]);// Customize iterable parameters
    const m3 = new Map({[Symbol.iterator]: function* (){
        yield ['key1'.'value1'];
        yield ['key2'.'value2'];
        yield ['key3'.'value3']; }});Copy the code
  • The key pairs of M2 and m3 are equal, but they themselves are not equal.

  • Use the size attribute to see the length of the key-value pair.

    m1.size; / / 0
    m2.size; / / 2
    m3.size; / / 3
    Copy the code

set

  • If you want to add values after the Map is initialized, use the set method. It takes keys and values as arguments in order.

  • Unlike Object, which can be set to any data type, neither the key nor the value is of any data type.

  • And after the call, the current object is returned, that is, it supports chained calls.

    m1.set('myKey1'.'myValue2');
    
    const symbolKey = Symbol(a);const functionKey = function(){};
    const booleanKey = false;
    const objectKey = {};
    
    m1.set(symbolKey, 'symbol');
    m1.set(functionKey, 'function');
    
    // String calls
    m1.set(booleanKey, true).set(objectKey, "{}");
    m1.size; / / 5
    Copy the code

get

  • Map values also have specific methods, and cannot be obtained directly in the form of access like objects.

    m1.get(symbolKey);
    m1.get(functionKey);
    
    // Change the attribute of a key or value without affecting the key value
    objectKey.key = 'key';
    m1.get(objectKey); / / "{}"
    Copy the code

has

  • The Map also have their own way to judge whether there is a key, similar to the Object. The prototype. HasOwnProperty.

    // Obviously not
    m1.has(123); // false
    
    m1.has(symbolKey); // true
    m1.has(objectKey); // true
    
    // Does not refer to the same reference
    m1.has(Symbol()); // false
    m1.has({}); // false
    Copy the code

delete

  • Map deletes the property using the delete method and returns a Boolean value indicating whether the key exists in the current key-value pair. In other words, it can also be used to detect whether the key exists, similar to has.

    m1.delete(functionKey); // true
    m1.delete(456); // false
    m1.size; / / 4
    Copy the code

clear

  • Map clears all key-value pairs using the clear method.

    m1.clear();
    m1.size; / / 0
    Copy the code

Sequence and iteration

  • Maps are iterable and remember the order in which key-value pairs were inserted.

entries

  • Get the key-value pairs in sequence.

    for (let pair of m2.entries()) {
      console.info(pair); // Output ['key1', 'value1'], etc
    }
    for (let pair of m2[Symbol.iterator]) {
      console.info(pair); // Output ['key1', 'value1'], etc
    }
    Copy the code

keys

  • Get the keys in order.

  • The current key can be modified during the traversal.

    • The original string cannot be modified.
    • The new key does not affect the original key value.
    for (let key of m2.keys()) {
      console.info(key); // key1 key2
      key = 'kkk';
      console.info(m2.get(key), m2.get('key1')); // undefined 'value1'
    }
    Copy the code

values

  • Get the values sequentially.

  • The current value can be modified during the iterate. It’s similar to a bond.

    • The original string cannot be modified.
    • The modified value can be retrieved using the original key.
    for (let value of m2.values()) {
      console.info(value); // value1 value2
      value = 'val';
      console.info(m2.get('key1'), value); // 'value1' 'val'
    }
    Copy the code

forEach

  • It is worth noting that the first argument of the traversal is the value and the second argument is the key.

    m2.forEach((value, key) = > {
      console.info(value, key); // Output ['value1', 'key1'], etc
    })
    Copy the code

Map and Object selection

  • For most developers, it’s more of a personal preference. The gap between the two is not as great as one might think.

Comparison of advantages and disadvantages

  • Memory footprint: Given a fixed size of memory, a Map can store 50% more memory
  • Insert performance: Maps perform slightly better when a large number of inserts are involved.
  • Search speed: Object is better when a large number of searches are involved. (Perhaps because Map maintains order)
  • Delete performance: Object delete has been criticized for its performance in different browsers, so Map is better.

Weak WeakMap mapping

define

  • Only objects can be used as key mappings, and when the key object loses reference, it will be garbage collected.

Basic API

Initialize the

  • The initialization method is similar to that of Map.

    const wm1 = new WeakMap(a);const wm2 = new WeakMap([[{},'{}'],
      [objectKey, {}]
    ]);
    // The following declaration will report an error because only objects can be used as keys
    const wm3 = new WeakMap([[12.34],
      ['str'.'string']]);// Thrown exception: Uncaught TypeError: Invalid value used as weak map key
    Copy the code

set/get/has/delete

  • WeakMap can also use these methods, but it does not support clear, nor does it have an iterative method, nor does it have a size attribute.

  • However, you can use the way to redefine a WeakMap to simulate the clear method.

    class ClearableWeakMap {
      constructor(init) {
        this._wm = new WeakMap(init);
      }
      clear() {
        return new WeakMap(a); }delete(key) {
        return this._wm.delete(key);
      }
      set(key, val) {
        this._wm.set(key, val);
        return this;
      }
      has(key) {
        return this._wm.has(key); }}Copy the code

Key recovery

  • Let’s look at a specific case where the key is recycled.

  • It is important to note that because garbage collection timing is difficult to verify, the results may be inaccurate when performed in a regular JS environment. You can try it manually by clicking Collect Garbage in Chrome’s Devtools.

  • Or if the following case is executed in the Node environment and the in-memory data is directly available, you can see the exact effect.

    // If the following weak mapping is defined, the value will never actually be obtained
    const wm3 = new WeakMap([[{}, {key: 'val '}]]);// There is actually no corresponding reference, it is empty
    wm3.get({}); // undefined
    
    // The reference is cleared
    const container = {
      key: {}}function removeReference() {
      container.key = null;
    }
    const m4 = new Map([
      [container.key, 'val']]);const wm4 = new WeakMap(a); wm4.set(container.key,'val');
    
    console.info(wm4.get(container.key), m4.get(container.key)); // There are key-value pairs
    removeReference();
    console.info(wm4.get(container.key), m4.get(container.key)); // wm has no key-value pairs
    Copy the code

This section describes the usage scenario of WeakMap

Deploying private variables

  • Use closures to prevent WM from being accessed externally.

    const User = (() = > {
      const wm = new WeakMap(a);class User {
        constructor(id) {
          this.idProperty = Symbol('id');
          this.setId(id);
        }
        setPrivate(property, value) {
          const privateMembers = wm.get(this) | | {}; privateMembers[property] = value; wm.set(this, privateMembers);
        }
        getPrivate(property) {
          const privateMembers = wm.get(this) | | {};return privateMembers[property];
        }
        setId(id) {
          return this.setPrivate(this.idProperty, id);
        }
        getId(){
          return this.getPrivate(this.idProperty); }}returnUser; }) ();const userA = new User('biaomianshiluanma');
    Copy the code

Stores DOM metadata

  • If the button is removed from the page, the reference is still there, taking up unnecessary memory, so it is better to use a weak mapping.

    const btnEle = document.querySelector('#btn');
    const domWm = new WeakMap(a);const domM = new Map(a); domwWm.set(btnEle, {disable: true });
    domM.set(btnEle, { disable: true });
    // When the DOM node is deleted from the page, the data stored in domWm is automatically reclaimed
    Copy the code

Set

define

  • Collection type: A collection of arbitrary values.
  • Values can be of any type, but they are always unique.

Basic API

  • Note that there is no way to value a Set.

Initialize the

  • Initialize a collection using an array.

  • It can also be initialized using an iterator.

    const s1 = new Set(['val1'.'val2'.'val3']);
    const s2 = new Set({[Symbol.iterator]: function* () {
        yield "val1";
        yield "val2";
        yield "val3"; }}); s1.size;/ / 3
    s2.size; / / 3
    Copy the code

add

  • After the Set is initialized, you want to continue adding values. You can use the add method, and the arguments can be of any type.

  • At the same time, it returns the currently invoked object, which means it also supports chained calls.

    const arr = [1.2.3.4];
    s1.add(4);
    s1.add({});
    s1.add(arr);
    s1.add(true);
    s2.add(4).add('5');
    s1.size; / / 7
    s2.size; / / 5
    Copy the code

has

  • Set also has its own method to determine if a key is present, similar to array.prototype.includes.

    s1.has(4); // true
    s1.has('4'); // false
    s1.has(arr); // true
    s1.has({}); // false does not refer to the same memory address
    s2.has('5'); // true
    Copy the code
  • Similar to Map, if you modify the properties of an object in a Set, the has method does not affect the judgment.

    const s3 = new Set(a);let obj = { name: 'kk' };
    s3.add(obj);
    obj.age = 29;
    // Only properties are updated
    s3.has(obj); // true
    obj = { alias: 'hh' };
    // Updated the reference
    s3.has(obj); // false
    Copy the code

delete

  • The Set is also deleted by delete, which also returns a Boolean value indicating whether the key exists.

    s2.delete(123123); // false
    s2.delete(2); // true
    Copy the code

clear

  • Set also uses clear to clear all values.

    s3.clear();
    s3.size; / / 0
    Copy the code

Sequence and iteration

  • The Set maintains the order in which values are stored.

keys/values

  • Since there is no concept of key-value pairs in a Set, the two return the same on a Set, returning a new iterator object.

    // Iteration results are the same. Iteration order is the order of insertion
    for (let key of s1.keys()) {
      console.info(key, 'key');
    }
    
    for (let val of s1.values()) {
      console.info(val, 'val');
    }
    
    // val1
    // val2
    // val3
    Copy the code

entries

  • Return as a key-value pair, that is, return two identical values.

    for (let o of s1.entries()) {
      console.info(o, 'entries');
    }
    
    // Get the following result
    // ['val1', 'val1']
    // ['val2', 'val2']
    // ['val3', 'val3']
    / / (4, 4)
    / / / '5', '5'
    
    // The following results are similar
    for (let [k, v] of s1.entries()) {
      console.info(k, v, 'entries');
    }
    Copy the code

forEach

  • Just like iterating through a Set, you can use forEach. To get results similar to the keys/values method.

    s1.forEach((value) = > console.info(value));
    // val1
    // val2
    // val3
    / / 4
    / / '5'
    Copy the code

Application scenarios of Set

  • The uniqueness of values makes sets a good place for reprocessing, as well as for getting intersections, unions, etc.

  • Here is the implementation:

    class XSet extends Set {
      isSuperXSet(set) {
        return XSet.isSuperXSet(this, set);
      }
      union(. sets) {
        return XSet.union(this. sets); }intersection(. sets) {
        return XSet.intersection(this. sets); }difference(sets) {
        return XSet.difference(this, sets);
      }
    
      symmtricDifference(sets) {
        return XSet.symmtricDifference(this, sets);
      }
    
      powerSet() {
        return XSet.powerSet(this);
      }
      
    	/** is a subset * of the other@param setA 
       * @param setB 
       * @returns * /
      static isSuperXSet(setA, setB) {
        for (const val of setA) {
          if(! setB.has(val)) {return false; }}return true;
      }
      
      /** * return union *@param setA 
       * @param setsB 
       * @returns * /
      static union(setA, ... setsB) {
        const unionSet = new XSet(setA);
        for (const valA of setA.values()) {
          for (const valB of setsB) {
            if (valB.has(valA)) {
              continue; } unionSet.add(valA); }}return unionSet;
      }
    
      /** * return intersection *@param setA 
       * @param setsB 
       * @returns * /
      static intersection(setA, ... setsB) {
        const intersectionSet = new XSet(setA);
        for (const valA of setA) {
          for (const bSet of setsB) {
            if (bSet.has(valA)) {
              continue; } intersectionSet.delete(valA); }}return intersectionSet;
      }
    
      /** * returns the difference set@param setA 
       * @param setB 
       * @returns * /
      static difference(setA, setB) {
        const differenceSet = new XSet(setA);
    
        for (const b of setB) {
          if(differenceSet.has(b)) { differenceSet.delete(b); }}return differenceSet;
      }
    
      /** * Symmetric difference set *@param setA 
       * @param setB 
       * @returns * /
      static symmtricDifference(setA, setB) {
        returnsetA.union(setB).difference(setA.intersection(setB)); }}// After instantiating, it is ready to use
    const xSet = new XSet([1.2.3.4]);
    const xSetChild = new XSet([1.2.3]);
    xSetChild.isSuperXSet(xSet); // true
    Copy the code

WeakSet weak collections

define

  • Weak collections. The value can only beObjectType or inherited fromObjectType.
  • An attempt to set a value that is not an object throws an exceptionTypeError.

Basic API

  • With the Set type, it also has these apis, but lessclearMethods. You can simulate it by redefining a WeakSetclear.

add

has

delete

Weak value

  • The particularity of weak collections makes them convenient for garbage collection.

    // Declare an empty object with no other reference to the object
    const wSet = new WeakSet([{}]);
    // After the declaration, the empty object is garbage collected, making the contents of the wSet empty
    console.info(wSet);
    // Sometimes there may be content printed out, it may be that the garbage collection of the browser has not been triggered, see the WeakMap introduced above
    Copy the code
  • Here’s an example that might be more intuitive.

    const container = {
      val: {}};const wSet1 = new WeakSet([container.val]);
    
    /** * Remove references */
    function removeReferrence() {
      container.val = null;
    }
    
    console.info(wSet1, wSet1.has(container.val)); // true
    removeReferrence();
    console.info(wSet1, wSet1.has(container.val)); // false
    Copy the code

noniterability

  • Because the values in a weak set are weak and can be destroyed at any time, there is no need to provide the ability to iterate.
  • The reason why WeakSet restricts only objects as values is to ensure that the value can be obtained only by reference to the value object. If raw values are allowed, there is no way to distinguish between a string literal used during initialization and an equivalent string used after initialization.

Application scenarios of WeakSet

  • WeakSet can also be used to store DOM metadata.

    const disabledElements = new WeakSet(a);const loginBtn = document.querySelector('#login');
    
    DisabledElements can free the corresponding memory as long as any button is removed from the page
    disabledElements.add(loginBtn);
    Copy the code

reference

Advanced Programming in JavaScript (version 4)

Some references