[es6]js中map、set及其weak类型的基本用法

一、Set(集合)

Set是一种在ES6中新增的数据结构,与数组不同的是其成员无重复无序。与Array一样,Set本身也是一个构造函数,可以从其身上new出新对象。

Set常用属性及增删改查方法:

  • size属性: 返回集合的元素个数。(类似数组的长度length)
  • add(value)方法: 向集合中添加一个元素value。注意:如果向集合中添加一个已经存在的元素,不报错但是集合不会改变。
  • delete(value)方法: 从集合中删除元素value。
  • has(value)方法: 判断value是否在集合中,返回true或false.
  • clear()方法: 清空集合。用法如下:
let mySet = new Set([12321]);
console.log(mySet.size);   //3
console.log(...mySet);      //1,2,3
mySet.add(4);
console.log(mySet.size);   //4
mySet.delete(3);
console.log(mySet.size);  //3
console.log(mySet.has(2));  //true
mySet.clear();
console.log(mySet.size);  //0

将Set转为数组: Array.from 方法可以将 Set 数据结构转为数组:

let mySet = new Set([12321]);
console.log(mySet instanceof Array);  //false
let myArr = Array.from(mySet);
console.log(myArr instanceof Array);  //true
console.log(myArr);  // [1,2,3]

Set集合遍历方法:

注意:遍历顺序为插入顺序

  • keys():返回一个包含集合中所有键的迭代器
  • values():返回一个包含集合中所有值得迭代器
  • entries():返回一个包含Set对象中所有元素得键值对迭代器
  • forEach(callbackFn, thisArg):用于对集合成员执行callbackFn操作,如果提供了 thisArg 参数,回调中的this会是这个参数,没有返回值
let mySet = new Set([12321]);
console.log(mySet.keys());// SetIterator {1, 2, 3}
console.log(mySet.values());// SetIterator {1, 2, 3}
console.log(mySet.entries());// SetIterator {1, 2, 3}
mySet.forEach(function(i){
 console.log(i+this);  // 11,12,13
},10)


二、WeakSet

WeakSet 与 Set 的区别:

  • WeakSet只能存放对象引用,而Set可以存放任何类型的值。
  • WeakSet 对象中储存的对象值都是被弱引用的,即垃圾回收机制不考虑 WeakSet对该对象的应用,如果没有其他的变量或属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet中),运行前后成员个数可能会不一致,遍历结束之后,有的成员可能取不到了(被垃圾回收)。
  • 其clear()方法不可用。
  • WeakSet对象是无法被遍历的(ES6 规定 WeakSet 不可遍历),也没有办法拿到它包含的所有元素
  • 其余无差别。

三、Map(字典)

集合与字典的区别:

  • 同: 集合和字典都是存储不重复的值.
  • 异: 集合中是以[value,value]存储的,字典中是以[key,value]存储的。

任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数,如下:

let Arr = [["name","jack"],["age","18"],["sex","man"]];
let myMap = new Map(Arr);
console.log(myMap.get("name")); //jack

如果传入的数组Key出现同名,键值将会被最后一个此键名的值的所覆盖,如:

let Arr = [["name","jack"],["name","Mary"],["age","18"],["sex","man"]];
let myMap = new Map(Arr);
console.log(myMap.get("name")); //Mary

特别注意: 只有对同一个对象的引用,Map 结构才将其视为同一个键。

let Arr = [[["name"],"jack"]];
let myMap = new Map(Arr);
console.log(myMap.get(["name"]));  //两个["name"]在内存中的地址不一样,此处输出undefined;

这样做就没问题:

let arr1 = ["name"];
let Arr = [[arr1,"jack"]];
let myMap = new Map(Arr);
console.log(myMap.get(arr1));   //输出jack;

Map常用属性及增删改查方法:

  • size: 属性,取出字典的长度
  • set(key, value):方法,向字典中添加新元素
  • get(key):方法,通过键查找特定的数值并返回
  • has(key):方法,判断字典中是否存在键key
  • delete(key):方法,通过键 key 从字典中移除对应的数据
  • clear():方法,将这个字典中的所有元素删除

用法如下:

let myMap = new Map();
myMap.set("name","Jack");    
myMap.set("age","18");     //添加元素
console.log(myMap.size);  //2
console.log(myMap.get("name"));//Jack
console.log(myMap.has("name")); //true
myMap.delete("age"); //删除
console.log(myMap.size); //1
console.log(myMap.has("age")); //false
myMap.clear();    //清空元素
console.log(myMap.size); //0

Map常用遍历方法:

  • Keys():将字典中包含的所有键名以迭代器形式返回
  • values():将字典中包含的所有数值以迭代器形式返回
  • entries():返回所有成员的迭代器
  • forEach():遍历字典的所有成员

Map遍历

let myMap = new Map();
myMap.set("name","Jack");
myMap.set("age","18");
myMap.set("sex","man");

myMap.forEach(
  (value,key,map)=>{
    console.log(value);    //Jack,18,man
    console.log(key);  //name,age,sex
    console.log(map); //自身,自身,自身
  }
)

Map转数组:用展开运算符:

let myMap = new Map();
myMap.set("name","Jack");
myMap.set("age","18");
myMap.set("sex","man");

console.log(...myMap); // [["name","Jack"],["age","18"],["sex","man"]];

四、WeakMap

按照MDN上的说明

WeakMap 对象是键/值对的集合,且其中的键是弱引用的。其键只能是对象,而值则可以是任意的。

从这段描述来看,我们可以大致推断出,WeakMap与Map的主要区别在于两点:

  1. WeakMap对key的引用是弱引用
  2. WeakMap的key只能是对象

即:WeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意。

注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 的 key 是不可枚举的。

对于不可枚举(不可遍历这一特点),MDN上是这样介绍的:

正由于这样的弱引用,WeakMap 的 key 是非枚举的 (没有方法能给出所有的 key)。如果key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果.

WeakMap的核心思想是“在不改变对象本身的情况下扩展对象”。

方法:

  • has(key):判断是否有 key 关联对象
  • get(key):返回key关联对象(没有则则返回 undefined)
  • set(key):设置一组key关联对象
  • delete(key):移除 key 的关联对象

五、小结

Set

  1. 成员唯一、无序且不重复
  2. [value, value],键值与键名是一致的(或者说只有键值,没有键名)
  3. 可以遍历,方法有:add、delete、has

WeakSet

  1. 成员都是对象
  2. 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
  3. 不能遍历,方法有add、delete、has

Map

  1. 本质上是键值对的集合,类似集合
  2. 可以遍历,方法很多可以跟各种数据格式转换

WeakMap

  1. 只接受对象作为键名(null除外),不接受其他类型的值作为键名
  2. 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的 不能遍历,方法有get、set、has、delete

补充:weak类型在垃圾回收中的应用

弱引用 weakmap和weakset

及时清除引用非常重要。但是实际开发中,有时候一疏忽就忘了,所以才有那么多内存泄漏。

最好能有一种方法,在新建引用的时候就声明,哪些引用必须手动清除,哪些引用可以不管,当引用消失以后,垃圾回收机制就可以释放内存了,这样就能大大减轻程序员的负担,你只要清除主要的引用就好了。

es6考虑到这点,提出了两种新的数据结构:weakset 和 weakmap 。他们对值的引用都是不计入垃圾回收机制的,所以名字里才会有一个weak,表示这个弱引用。

下面以 WeakMap 为例,看看它是怎么解决内存泄漏的。

const wm = new WeakMap();
const element = document.getElementById('example');
vm.set(element,'something');
vm.get(element);

上面代码中,先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例,并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。

也就是说,DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用,它占用的内存就会被垃圾回收机制释放。Weakmap 保存的这个键值对,也会自动消失。

基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。

什么是内存泄漏?

程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存。

对于持续进行的服务进程(daemon),必须及时释放内存,否则内存占用会越来越高,影响系统性能,直至进程崩溃。

所以,不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)

有些语言,比如C语言,必须手动释放内存,程序员负责内存管理。

char * buffer;
buffer = (char*) malloc(42);

// do sometjing with buffer

free(buffer);
123456

上面是 C 语言代码,malloc方法用来申请内存,使用完毕之后,必须自己用free方法释放内存。

这很麻烦,所以大多数语言提供自动内存管理,减轻程序员的负担,这被称为“垃圾回收机制”(garbage collector)。

分析 JavaScript 代码的内存使用

timeline 图及 profile

名词解释:

  • GC 垃圾回收:Garbage collection
  • DOM GC DOM 气相色谱
  • majory GC
  • heap 堆
  • GPU memery GPU 存储器
  • timings 计时
  • timeline 时间线
  • call tree 调用数
  • screen shots 屏幕截图

内存泄漏的场景

  • 意外的全局变量

    function foo(arg{
      bar = "this is a hidden global variable";
    }

    function foo({
      this.variable = "potential accidental global";
    }
    foo();

    解决:'use strict;'

  • 被遗忘的定时器器或回调

    var someResource = getData();
    setInterval(function({
      var node = document.getElementById('Node');
      if(node) {
        // Do stuff with node and someResource.
        node.innerHTML = JSON.stringify(someResource));
      }
    }, 1000);

    var element = document.getElementById('button');
    function onClick(event{
      element.innerHtml = 'text';
    }
    element.addEventListener('click', onClick);
    // Do stuff
    element.removeEventListener('click', onClick);
    element.parentNode.removeChild(element);
    // 现在,当元素超出范围,元素和onclick都将被回收
  • 闭包

    var theThing = null;
    var replaceThing = function({
      var originalThing = theThing;
      var unused = function({
        if (originalThing) console.log("hi");
      };
    };
    setInterval(replaceThing, 1000);

垃圾回收机制

最常用的方法叫做“引用计数”(reference counting):语言引擎中有一张“引用表”,保存了内存里所有的资源的引用次数。如果一个值的引用次数是0,就表示这个值不再使用了,因此可以将这块内存释。

如果一个值不再需要了,引用数却不为0,垃圾回收机制无法释放这块内存,从而导致内存泄漏。

const arr = [1,2,3,4];
console.log('hello');
12

上面代码中,数组[1, 2, 3, 4]是一个值,会占用内存。变量arr是仅有的对这个值的引用,因此引用次数为1。尽管后面的代码没有用到arr,它还是会持续占用内存。

如果增加一行代码,解除arr对[1, 2, 3, 4]引用,这块内存就可以被垃圾回收机制释放了。

let arr = [1,2,3,4];
console.log('hello');
arr = null;
123

上面代码中,arr重置为null,就解除了对[1, 2, 3, 4]的引用,引用次数变成了0,内存就可以释放出来了。

因此,并不是说有了垃圾回收机制,程序员就轻松了。你还是需要关注内存占用:那些很占空间的值,一旦不再用到,你必须检查是否还存在对它们的引用。如果是的话,就必须手动解除引用。

内存泄漏识别

怎样可以观察到内存泄漏呢?

经验法则是,如果连续五次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。这就要求实时查看内存占用。

Chrome浏览器查看内存占用情况,按以下步骤操作:

  • 打开开发者工具,选择 Timeline 面板
  • 在顶部的Capture字段里面勾选 Memory
  • 点击左上角的录制按钮。
  • 在页面上进行各种操作,模拟用户的使用情况。
  • 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。

如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。反之,就是内存泄漏了。

内存泄漏的解决

  • 手动释放

    let arr = [1234]; 
    // 尽管没有用到这个变量,但是变量arr对[1, 2, 3, 4]有引用,所以这个值不能得到释放
    arr = null// 解除arr对值的引用,内存就会被释放了
  • 使用弱引用:WeakSetWeakMap

    const wm = new WeakMap();
    const element = document.getElementById("example");
    wm.set(element, "some information");
    wm.get(element); // "some information"

参考资料

https://zhuanlan.zhihu.com/p/25454328


原文始发于微信公众号(豆子前端):[es6]js中map、set及其weak类型的基本用法

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/56828.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!