今日说一下 Map,WeakMap

  • 是由于今日突然想起来之前看的一篇怎样写出一个惊艳面试官的深复制?里边提到了运用weakMap可以起到画蛇添足的作用,燃起了我的爱好,有必要好好地搞懂它

完全搞懂WeakMap和Map

  • 要搞懂weakMap之前,咱们肯定要先搞定Map

一 、Map

Map是一种叫做字典的数据结构,Map目标保存不重复键值对,并且可以记住键的原始刺进次序

  • Map的特点和办法
    • 特点: size: 回来所包括的键值对长度
    • 操作办法:
      • set(key,val): 添加新键值对
      • get(key): 经过传入键查找并回来对应值
      • has(key): 判别是否存在该键
      • delete(key):删去该键值对
      • clear:清空
//创立map结构
const map = new Map();
//添加键
map.set('name','clt').set('gender','girl').set('age',20);
//获取size
console.log(map.size); //3
//判别键
console.log(map.has('age')); //20
//获取值
console.log(map.get('age')); //true
//清空map
map.clear()
console.log(map.size); //0

✍问题一:能不能对Map设置目标特点?

  • 你带着疑问输入了以下代码,发现没有报错并且的确打印出来了,可是定睛一看,size=0
    const map = new Map();
    map.gender = 'girl';
    map['name'] = 'clt';
    console.log(map); //{gender: 'girl', name: 'clt', size: 0}
    
  • 由于这种办法只能说是目标的特性,可是数据并没有进入map的数据结构,也就是它并没有被map存储起来,所以size为0,同样其他操作办法也不生效;所以答案是可以,可是没有用
    console.log(map.get('name')) //undefined
    console.log(map.has('gender')); //false
    

✍问题二:已然是存储不重复的数据,那么是怎样判别是否持平?

  • 主要是判别键是否持平
  • 键的判别是根据零值持平(SameValueZero
  • ===的区别为关于===而言NaN !== NaN, 而关于零值运算而言两者是持平

这儿我看过有一些说法是键的比较运用Object.is(),我感觉不太对?,Object.is()认为+0,-0不持平,而Map是认为持平的

const map = new Map();
map.set(+0,'123').set(-0,'345');
console.log(map); //0,345
console.log(Object.is(-0,+0)) //false

✍问题三:Map和Object的区别在哪里?

  • 键值Map可以是恣意值,包括函数,目标,根本类型,而目标只能是String或者Symbol或者整数

  • 键值对长度Map可以直接经过size特点获取键值对长度,而Object没有供给该特点,需要手动遍历核算

  • 迭代Map是可迭代的,有内置迭代器,可是Object没有完成迭代协议,没有内置迭代器

    console.log(typeof obj[Symbol.iterator]); //undefined 
    console.log(typeof map[Symbol.iterator]); //function
    
  • 功能Map在频频增删键值对的场景下表现更好,而Object在频频添加和删去键值对的场景下未作出优化

  • 序列化JSON支撑Object,可是尚未支撑Map

    const obj = {
      name: 'clt',
      age: 20
    }
    const map = new Map();
    map.set('name','clt').set('age',20);
    console.log(JSON.stringify(obj)); //{"name":"clt","age":20}
    console.log(JSON.stringify(map));//{} 
    
  • 有序Map是有序的,可是Object不能保证是有序的

    • Map有序咱们前面现已提过了,试一下Object是否可以保证有序
            //比如我创立一个如下的Object
            const o = Object.create(null, {
              m: {value: function() {}, enumerable: true},
              "2": {value: "2", enumerable: true},
              "b": {value: "b", enumerable: true},
              0: {value: 0, enumerable: true},
              [Symbol()]: {value: "sym", enumerable: true},
              "1": {value: "1", enumerable: true},
              "a": {value: "a", enumerable: true},
            });
      
    • 直接打印成果(chrome,这个成果在不同浏览器上不一定一致)
      完全搞懂WeakMap和Map
    • for-in遍历打印成果

    完全搞懂WeakMap和Map

  • 看完现已很烦了❌?上图✔️

    完全搞懂WeakMap和Map

✍ 问题四:什么时分运用Map

  • 向Object和Ma刺进新键值对的耗费适当,可是一般刺进Map会比较快一点,所以假如代码涉及很多刺进操作,那么Map的功能更加
  • 当咱们只需要一个简单的可查找存储结构时,Map相比Object更具优势,它供给了一切根本操作,可是假如涉及很多查找情况下,可能挑选Object会好一点
  • 假如涉及很多删去操作,美美挑选Map啦

问题五:上面提到了可以迭代,那么是怎样迭代呢

  • Map可以运用for..of循环来完成迭代
    //创立map结构
    const map = new Map();
    //添加键
    map.set('name','clt').set('gender','girl').set('age',20);
    for(let [key,item] of map) {
      console.log(key +'='+ item)
    }
    
  • Map也可以经过forEach()办法迭代,传入回调函数顺次迭代每个键值对,有可选的第二个参数,用于重写回调内部this的值
    //创立map结构
    const map = new Map();
    //添加键
    map.set('name','clt').set('gender','girl').set('age',20);
    map.forEach((value, key) => {
      console.log(key + '=' + value)
    })
    

    这种行为跟在数组上调用forEach()办法略有不同,后者的回调函数会按照数值索引的次序接收到每一个项,

问题六、是否可以一次性将很多数据添加到Map

  • 可以在构造Map的时分传入数组来初始化,该数组的每一项也有必要是数组,内部首个项作为键,第二项作为对应值
    const arr = [['name', 'mouche'] ,[ 'age', 20]];
    const map = new Map(arr);
    for(let [key, value] of map) {
      console.log(key+'='+ value);
    } 
    //name=mouche, age=20
    

✍ 问题七、怎样将map转换为数组

  • 经过Array.from()
  • 经过展开运算符

Map就说到这儿了,你认为我要说WeakMap? 漏!搞定了废物收回才干搞懂WeakMap,所以咱们就先来看看这个是啥玩意

二、废物收回

  • 原因:假如存在很多不被开释的内存(堆/栈/上下文),页面功能会变得很慢。废物收回望文生义就是收回废物嘛,那么当某些代码操作不能被合理开释,就会形成内存走漏,所以这个时分废物收回就出来发挥作用了
  • 浏览器废物收回机制有两种,咱们别离来看看他们是啥玩意

1、符号清理

  • 2012年后一切浏览器都运用了这种战略
    • 废物收回器,在运行的时分会给存储在内存中的一切变量都加上符号
    • 去掉环境中的变量以及被环境中的变量引证的变量的符号
    • 此时还被加上符号的会被视为准备删去的变量。
    • 废物收回器完成内存铲除工作,毁掉那些带符号的值并收回他们所占用的内存空间
  • 缺陷:地址不接连,空间碎片化

2、引证计数

  • 坏处较多,IE9之前采用
  • 对每个值记载其被引证的次数,经过最终对次数的判别来决议是否保留
  • 最大问题:无法解决循环引证无法收回的问题

三、WeakMap

  • WeakMap目标是一组键/值对的调集,其中的键是弱引证的。其键有必要是目标(null除外),而值可以是恣意的
const wm = new WeakMap();
wm.set(null,'123');

完全搞懂WeakMap和Map

  • 弱引证:是指不能保证其引证的目标不会被废物收回器收回的引证。一个目标若只被弱引证所引证,则被认为是不可拜访(或弱可拜访)的,并因而可能在任何时刻被收回
  • 也就是说,当WeakMap键所指目标没有其他地方引证的时分,它会被废物收回机制收回掉

理解了废物收回机制再看这句话是不是就没有那么迷糊啦,拿捏

  • WeakMap 不支撑迭代以及 keys()values()entries() 办法
  • WeakMap的办法
    • get(key):获取键对应值
    • set(key,value):存储键值对
    • delete(key): 删去键值对
    • has(key): 判别是否有该键值对

问题一:已然现已有map了,那么WeakMap出现的原因是什么

  • 那么就先说一下map的问题;map经过使其四个API办法共用两个数组(一个寄存键,一个寄存值)来完成。给这种map设置值时会一起将键和值添加到这两个数组的末尾。然后使得键和值的索引在两个数组中相对应。当从该map取值的时分,需要遍历一切的键,然后运用索引从存储值的数组中检索出相应的值
  • 这就导致了无论是赋值还是搜索都需要遍历真个数组,时刻复杂度为O(n)
  • 一起也可能导致内存走漏,由于数组会一向引证这每个键和值,导致无法废物收回机制无法在他们没有其他引证的时分进行收回

问题二:为什么不支撑迭代

  • 当键所指目标没有被其他地方引证的时分就会被收回,那么没有办法能给出一切的 key,即它的成果是不确定的,所以就没有必要供给迭代办法,所以也没有clear办法

✍ 问题三:WeakMap怎样用

DOM节点元数据
  • 由于WeakMap不会阻碍废物收回,所以非常合适保存相关元数据
    const wm = weakMap();
    cons loginButton = document.querySelector('#login');
    wm.set(loginButton,{disabled:true})
    
  • 如上运用WeakMap,当节点从DOM树被删去后,废物收回程度就可以立即开释其内存(假如没有其他地方引证),可是假如你运用Map的话,即时节点删去后,由于映射中还保存着按钮的引证,所以DOM节点依旧会留在内存
私有变量
  • 先说一下ES6是怎样创立私有变量,此处运用IIFE,含有privateDataprivateId两个私有变量,当Person构造器被运用的时分,一个不可枚举,不可装备,不可写入的_id特点就被添加了。可是最大的问题是privateData里边的数据永不会消失,由于在目标实例被毁掉的时分没有任何办法可以获取这个数据,所以privateData就会就永远会包括剩余的数据
const Person = (function() {
  const privateData = {};
  let  privateId = 0;
  function Person(name) {
    Object.defineProperty(this,'_id' , {value:privateId++});
    privateData[this._id] = {
      name
    }
  }
  Person.prototype.getName = function() {
    return privateData[this._id];
  }
  return Person;
}())
  • 可以运用WeakMap来解决这个问题,由于Person实例本身能被作为键来运用,所以就没有必要记载ID,当Person构造器被调用时,this作为键,包括数据的目标即成了对应的值,getName()可以提取其私有信息。当私有信息与之相关的目标实例被毁掉的时分,私有信息也会一起被毁掉,也就解决了上述的问题
const Person = (function(){
  const privateData = new WeakMap();
  function Person(name) {
    privateData.set(this,{name});
  }
  Person.prototype.getName = function() {
    return privateData.get(this).name;
  }
  return Person
}())
const person = new Person('123');
console.log(person.getName()) //123