七: ES6 Set Map

前言

该部分为书籍 深入理解ES6 第七章(Set与Map)笔记

ES5中的 Set 与 Map

  • 在 ES5 中, 一般使用对象属性来模拟 Set 与 Map

    let set = Object.create(null);
    
    set.foo = true;
    
    // 检查属性的存在性
    if (set.foo) {
        
        // 一些操作
    }
    
  • 使用对象模拟 Map 与 Set 之间唯一真正的区别是所存储的值

    与 Set 不同, Map 多数被用来提取数据,而不是仅检查键的存在性。

    let map = Object.create(null);
    
    map.foo = "bar";
    
    // 提取一个值
    let value = map.foo;
    
    console.log(value); // "bar"
    

变通方法的问题

  1. 由于对象属性的类型必须为字符串,你就必须保证任意两个键不能被转换为相同的字符串。

    let map = Object.create(null);
    
    map[5] = "foo";
    
    console.log(map["5"]); // "foo"
    
  2. 若使用对象作为键, 就会出现另一个问题(对象会被转换为字符串[object Object])

ES6的 Set

ES6 新增了 Set 类型, 这是一种无重复值的有序列表.(Set 不是一种基础类型, 还是 Object 类型之上封装的一种数据结构, [[class]] 属性为 "Set")

Set 允许对它包含的数据进行快速访问,从而增加了一个追踪离散值的更有效方式。

1. 创建 Set

  • 使用 new Set()来创建

    在 Set 内部的比较使用了 Object.is() 方法, 来判断两个值是否相等, 唯一的例外是 +0 与-0 在 Set 中被判断为是相等的

    // 参数: iterable
    // 如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set中。如果不指定此参数或其值为null,则新的 Set为空。
    
    let set = new Set([iterable]?);
    

2. 方法以及属性

  1. Set.prototype.add(value): 添加项目

    如果 add() 方法用相同值进行了多次调用,那么在第一次之后的调用实际上会被忽略

    set.add(5);
    
  2. Set.prototype.delete(value): 删除值

    移除Set的中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,返回true,否则返回false)。Set.prototype.has(value)在此后会返回false。

  3. Set.prototype.clear():删除所有值

  4. Set.prototype.has(value): 判断值是否存在

  5. Set.prototype.size: 属性, 返回Set对象的值的个数。

3. 迭代方法

  1. Set.prototype.forEach(callbackFn[, thisArg])

    因为 Set 并没有数组或者对象概念的索引或键, 为了与其他数据结构的 forEach 保持一致, 回调函数的第一个与第二个参数是相同的

    要记住,虽然 Set 能非常好地追踪值,并且 forEach() 可以让你按顺序处理每一项,但是却无法像数组那样用索引来直接访问某个值。

    let set = new Set([1, 2]);
    
    /*
     * 回调函数接受三个参数: 
     * Set 中下个位置的值
     * 与第一个参数相同的值
     * 目标 Set 自身
    */
    set.forEach(function(value, key, ownerSet) {
     console.log(key + " " + value);
     console.log(ownerSet === set);
    });
    
  2. Set.prototype.keys()

    与values()方法相同,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。

  3. Set.prototype.values()

    返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。

  4. Set.prototype.entries()

    返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。为了使这个方法和Map对象保持相似, 每个值的键和值相等。

4.将 Set 转化为数组

  • 数组转 Set

    直接将数组传递给 Set 构造器

  • Set转 数组

    使用 扩展运算符

let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];

console.log(array); // [1,2,3,4,5]

可以用这个来快速进行数组去重

function eliminateDuplicates(items) {
    return [...new Set(items)];
}

ES6中 Weak Set

由于 Set 类型存储对象引用的方式,它也可以被称为 Strong Set 。对象存储在 Set 的一个实例中时,实际上相当于把对象存储在变量中。只要对于 Set 实例的引用仍然存在,所存储的对象就无法被垃圾回收机制回收,从而无法释放内存。

let set = new Set(),
    key = {};

set.add(key);
console.log(set.size); // 1

// 取消原始引用
// 将 key 设置为 null 清除了对 key 对象的一个引用,但是另一个引用还存于set 内部
key = null;

console.log(set.size); // 1

// 重新获得原始引用
key = [...set][0];

为了缓解这个问题, ES6 也包含了 Weak Set ,该类型只允许存储对象弱引用,而不能存储基本类型的值。对象的弱引用在它自己成为该对象的唯一引用时,不会阻止垃圾回收

1. 创建 Weak Set

  • 使用 new WeakSet()构造器

    **注意: ** Weak Set 项不能存在非对象的值, 如果传入了非对象的值, 就会抛出错误

    // [iterable]: 如果传入一个可迭代对象作为参数, 则该对象的所有迭代值都会被自动添加进生成的 WeakSet 对象中。null 被认为是 undefined。
    let set = new WeakSet([iterable])
    
  • 方法

    1. WeakSet.prototype.add(value): 添加项
    2. WeakSet.prototype.has(value): 判断项
    3. WeakSet.prototype.delete(value): 删除项

2. 与 Set 类型的差异

Weak Set 看起来功能有限,而这对于正确管理内存而言是必要的。一般来说,若只想追踪对象的引用,应当使用 Weak Set 而不是正规 Set 。

  1. 最大区别是对象的弱引用(即当其他对象没有引用时, 就会被环境所回收)

    JS 引擎垃圾回收机制最常见的就是标记清除法(可参考JavaScript 高级程序设计书)

  2. 对于 WeakSet 的实例,若调用 add() 方法时传入了非对象的参数,就会抛出错误(has() 或 delete() 则会在传入了非对象的参数时返回 false );

  3. Weak Set 不可迭代,因此不能被用在 for-of 循环中;

  4. Weak Set 无法暴露出任何迭代器(例如 keys() 与 values() 方法),因此没有任何编程手段可用于判断 Weak Set 的内容;

  5. Weak Set 没有 forEach() 方法;

  6. Weak Set 没有 size 属性。

ES6中的Map

ES6的 Map 类型是键值对的有序列表, 而键和值都可以是任意类型 . 键的比较使用的是 Object.is(), 因此 5"5" 同时作为键, 因为它们类型不同.

1. 创建 Map

  • new Map([iterable])

    在方法内部使用的是 Object.is() , 与 new Set() 类似

    // Iterable 可以是一个数组或者其他 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, 'one' ],[ 2, 'two' ]])。 每个键值对都会添加到新的 Map。null 会被当做 undefined。
    
    let set = new Set([iterable]?);
    

2. 方法 和 属性

  1. Map.prototype.size: 返回Map对象的键/值对的数量。

  2. Map.prototype.get(key): 返回键对应的值,如果不存在,则返回undefined。

  3. Map.prototype.set(key, value): 设置Map对象中键的值。返回该Map对象。

  4. Map.prototype.has(key): 返回一个布尔值,表示Map实例是否包含键对应的值。

  5. Map.prototype.delete(key): 删除项

    如果 Map 对象中存在该元素,则移除它并返回 true;否则如果该元素不存在则返回 false。随后调用 Map.prototype.has(key) 将返回 false 。

  6. Map.prototype.clear(): 移除Map对象的所有键/值对 。

3. 迭代方法

  1. Map.prototype.forEach(callbackFn[, thisArg]): 按插入顺序,为 Map对象里的每一键值对调用一次callbackFn函数。如果为forEach提供了thisArg,它将在每次回调中作为this值。
  2. Map.prototype.keys(): 返回一个新的 Iterator对象, 它按插入顺序包含了Map对象中每个元素的键 。
  3. Map.prototype.values():返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的值 。
  4. Map.prototype.entries(): 返回一个新的 Iterator 对象,它按插入顺序包含了Map对象中每个元素的 [key, value] 数组。

4. 与普通对象(Object)的区别

  • 一个Object的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。

  • Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。

    注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。**

  • 你可以通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。

  • Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代。

  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

    注意:虽然 ES5 开始可以用 map = Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见

  • Map 在涉及频繁增删键值对的场景下会有些性能优势。

ES6中的 Weak Map

Weak Map 对 Map 而言,就像 Weak Set 对 Set 一样: Weak 版本都是存储对象弱引用的方式。在 Weak Map 中,所有的键都必须是对象(尝试使用非对象的键会抛出错误),而且这些对象都是弱引用,不会干扰垃圾回收。当 Weak Map 中的键在 Weak Map 之外不存在引用时,该键值对会被移除。

必须注意的是, Weak Map 的键才是弱引用,而值不是。在 Weak Map 的值中存储对象会阻止垃圾回收,即使该对象的其他引用已全都被移除。

1. 创建 Weak Map

  • 使用 new WeakMap() 构造器

    键必须是非空的对象, 否则会抛出错误

    值则允许是任意类型

    // Iterable 是一个数组(二元数组)或者其他可迭代的且其元素是键值对的对象。每个键值对会被加到新的 WeakMap 里。null 会被当做 undefined。
    new WeakMap([iterable]?)
    
  • 方法

    1. WeakMap.prototype.set(key, value): 在WeakMap中设置一组key关联对象,返回这个 WeakMap对象。
    2. WeakMap.prototype.get(key): 返回key关联对象, 或者 undefined(没有key关联对象时)。
    3. WeakMap.prototype.has(key): 根据是否有key关联对象返回一个Boolean值。
    4. WeakMap.prototype.delete(key): 移除key的关联对象。执行后 WeakMap.prototype.has(key)返回false。

2. Weak Map 的用法与局限性

当决定是要使用 Weak Map 还是使用正规 Map 时,首要考虑因素在于你是否只想使用对象类型的键。如果你打算这么做,那么最好的选择就是 Weak Map 。因为它能确保额外数据在不再可用后被销毁,从而能优化内存使用并规避内存泄漏。

Weak Map 值为他们的内容提供了很小的可见度, 因此你不能使用 forEach() 方法、size 属性 或 clear() 方法来管理其中的项

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,458评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,030评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,879评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,278评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,296评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,019评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,633评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,541评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,068评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,181评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,318评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,991评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,670评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,183评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,302评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,655评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,327评论 2 358

推荐阅读更多精彩内容