zoukankan      html  css  js  c++  java
  • 【读书笔记】【深入理解ES6】#7-Set集合和Map集合

    ES6新标准中将Set集合和Map集合添加到JS中。

    ES5中Set集合和Map集合

    在ES5中,开发者们用对象属性来模拟这两种集合。

    var set = Object.create(null);
    
    set.foo = true;
    
    // 检查属性是否存在
    if (set.foo) {
        // ...
    }
    

    模拟Map集合同上例类似。

    如果只是简单的应用上面的方法基本上能满足需求,但是如果碰到对象属性名的限制,就会产生一些问题。

    var map = Object.create(null);
    map[5] = "foo";
    console.log(map["5"]); // "foo"
    console.log(map); // {5: "foo"}
    

    虽然map中存储的属性键名是数值型的5,但 map["5"] 引用的是同一个属性。

    用对象作为键名也有类似的问题。

    var map = Object.create(null),
        key1 = {},
        key2 = {};
    map[key1] = "foo";
    console.log(map[key2]); // "foo"
    console.log(map); // {[object Object]: "foo"}
    

    上例中 map[key1] 和 map[key2] 也引用了同一个值。因为 key1 和 key2 会被转换为对象的默认字符串 "[object Object]"。

    ES6中的Set集合

    ES6中新增的Set集合是一种有序列表,其中还有一些相互独立的非重复值,通过Set集合可以快速访问其中的数据,更有效地追踪各种离散值。

    创建Set集合并添加元素

    let set = new Set();
    set.add(5);
    set.add("5");
    
    console.log(set.size); // 2
    console.log(set); // Set(2) {5, "5"}
    

    向Set集合中添加对象

    let set = new Set(),
        key1 = {},
        key2 = {};
    
    set.add(key1);
    set.add(key2);
    
    console.log(set.size); // 2
    console.log(set); // Set(2) {{…}, {…}}
    

    如果多次传入相同的值,后面的调用实际上会被忽略。

    let set = new Set();
    set.add(5);
    set.add("5");
    set.add(5); // 重复 - 本次调用直接被忽略
    
    console.log(set.size); // 2
    console.log(set); // Set(2) {5, "5"}
    

    使用数组来初始化Set集合,并且同时可以出去重复的元素。

    let set = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
    console.log(set.size); // 5
    console.log(set); // Set(5) {1, 2, 3, 4, 5}
    

    Note

    实际上,Set构造函数可以接受所有可迭代对象作为参数,数组、Set集合、Map集合都是可迭代的,因而都可以作为Set构造函数的参数使用;构造函数通过迭代器从参数中提取值。

    通过 has() 方法可以检测Set集合中是否存在某个值

    let set = new Set();
    set.add(5);
    set.add("5");
    console.log(set.has(5)); // true
    console.log(set.has(6)); // false
    

    移除元素

    delete() 方法可以移除Set集合中的某一个元素;
    clear() 方法可以移除集合中所有元素。

    let set = new Set();
    set.add(5);
    set.add("5");
    
    console.log(set.has(5)); // true
    console.log(set.size); // 2
    
    set.delete(5);
    
    console.log(set.has(5)); // false
    console.log(set.size); // 1
    
    set.clear();
    
    console.log(set.has("5")); // false
    console.log(set.size); // 0
    

    Set集合的forEach()方法

    forEach() 方法的回调函数接受以下3个参数:

    1. Set集合中下一次索引的位置
    2. 与第一个参数一样的值
    3. 被遍历的Set集合本身

    需要注意的是这里的第一和第二个参数是一样的。这是为了和数组和Map集合的forEach()方法统一。

    let set = new Set([1, 2]);
    
    set.forEach(function(value, key, ownerSet) {
        console.log(key + " " + value);
        console.log(ownerSet === set);
    });
    
    // 输出结果:
    // 1 1
    // 5 true
    // 4 2 2
    // 5 true
    

    如果需要在回调函数中使用this引用,则要将this作为第二个参数传入forEach()函数。

    let set = new Set([1, 2]);
    
    let processor = {
        output(value) {
            console.log(value);
        },
        process(dataSet) {
            dataSet.forEach(function(value) {
                this.output(value);
            }, this);
        }
    };
    
    processor.process(set);
    

    这里可以使用箭头函数,这样就可以不用再将this传入forEach()方法了。

    let set = new Set([1, 2]);
    
    let processor = {
        output(value) {
            console.log(value);
        },
        process(dataSet) {
            dataSet.forEach(value => this.output(value));
        }
    };
    
    processor.process(set);
    

    将Set集合转换为数组

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

    使用Set集合实现数组的去重

    function eliminateDuplicates(items) {
        return [...new Set(items)];
    }
    
    let numbers = [1, 2, 3, 3, 3, 4, 5],
        noDuplicates = eliminateDuplicates(numbers);
    
    console.log(noDuplicates);
    

    Weak Set 集合

    弱引用的Set集合。用法同Set集合基本一样。

    let set = new WeakSet(),
        key = {};
    
    // 向集合中添加对象
    set.add(key);
    
    console.log(set.has(key)); // true
    
    set.delete(key);
    
    console.log(set.has(key)); // false
    

    两种Set类型的主要区别

    Weak Set保存的是对象值得弱引用。
    当Weak Set中的某个对象的所有强引用都被移除的时候,该对象也会自动从Weak Set中移除。

    let set = new WeakSet(),
        key = {};
    
    // 向集合中添加对象
    set.add(key);
    
    console.log(set.has(key)); // true
    
    // 移除对象key的最后一个强引用(Weak Set中的引用也会自动移除)
    key = null;
    

    Weak Set 集合和普通 Set 集合的差别

    1. 在WeakSet的实例中,如果向 add()、has()和delete()这3个方法传入非对象参数都会导致程序报错;
    2. Weak Set 集合不可迭代,所以不能被用于 for-of 循环;
    3. Weak Set 集合不暴露任何迭代器(例如 keys() 和 values() 方法),所以无法通过程序本身来检测其中的内容;
    4. Weak Set 集合不支持 forEach() 方法;
    5. Weak Set 集合不支持 size 属性。

    Weak Set 集合的功能看似受限,其实这是为了让它能够正确的处理内存中数据。
    总之,如果你只需要跟踪对象引用,你更应该使用 Weak Set 集合而不是 Set 集合。

    ES6中的 Map 集合

    ES6中的 Map 类型是一种储存着许多键值对的有序列表,其中键名和对应的值支持所有的数据类型。
    键名的等价判断是通过 Object.is() 方法实现的。

    let map = new Map();
    map.set("site", "liujiajia.me");
    map.set("year", 2017);
    
    console.log(map.get("site")); // "liujiajia.me"
    console.log(map.get("year")); // 2017
    

    使用对象作为键名

    let map = new Map,
        key1 = {},
        key2 = {};
    
    map.set(key1, 9);
    map.set(key2, 32);
    
    console.log(map.get(key1)); // 9
    console.log(map.get(key2)); // 32
    

    Map集合支持的方法

    Map集合和Set集合有如下3个通用方法:

    • has(key)
      检测指定的键名在Map集合中是否存在
    • delete(key)
      从Map集合中移除指定键名及其对应的值
    • clear()
      移除Map集合中所有的键值对
    let map = new Map();
    map.set("name", "JiaJia");
    map.set("age", 32);
    
    console.log(map.has("name")); // true
    console.log(map.get("name")); // "JiaJia"
    console.log(map.has("age")); // true
    console.log(map.get("age")); // 32
    console.log(map.size); // 2
    
    map.delete("name");
    console.log(map.has("name")); // false
    console.log(map.get("name")); // undefined
    console.log(map.size); // 1
    
    map.clear();
    console.log(map.has("name")); // false
    console.log(map.get("name")); // undefined
    console.log(map.has("age")); // false
    console.log(map.get("age")); // undefined
    console.log(map.size); // 0
    

    Map集合的初始化方法

    可以传入一个数组来初始化Map集合。
    数组中的每一个元素都是一个子数组,子数组中包含一个键值对的键名和值两个元素。

    let map = new Map([["name", "JiaJia"], ["age", 32]]);
    
    console.log(map.has("name")); // true
    console.log(map.get("name")); // "JiaJia"
    console.log(map.has("age")); // true
    console.log(map.get("age")); // 32
    console.log(map.size); // 2
    

    Map集合的forEach()方法

    和数组的forEach()方法类似,回调函数都接受3个参数:

    • Map集合中下一次索引的位置
    • 值对应的键名
    • Map集合本身
    let map = new Map([["name", "JiaJia"], ["age", 32]]);
    
    map.forEach(function(value, key, ownerMap) {
        console.log(key + " " + value);
        console.log(ownerMap === map);
    });
    // 执行结果:
    // name JiaJia
    // true
    // age 32
    // true
    

    同Set集合一样,可以指定forEach()方法的第二个参数作为回调函数的this值。

    Weak Map 集合

    Weak Map 是弱引用的 Map 集合,也用于存储对象的弱引用。

    • Weak Map 集合中的键名必须是对象;
    • 只有键名保存的是弱引用,键名对应的值如果是个对象,则保存的是该对象的强引用。

    Weak Map 最大的用途是保存Web页面中的DOM元素。

    let map = new WeakMap(),
        element = document.querySelector(".element");
    
    map.set(element, "Original");
    
    let value = map.get(element);
    console.log(value); // "Original"
    
    // 移除element元素
    element.parentNode.removeChild(element);
    element = null;
    
    // 此时 Weak Map 集合为空
    

    私有对象数据

    Weak Map 还可以用于存储对象实例的私有数据。
    在ES6中所有属性都是公开的。如果想存储一些只对对象开放的数据,则需要一些创造力。

    下例使用约定作为私有属性:

    function Person(name) {
        this._name = name;
    }
    
    Person.prototype.getName = function() {
        return this._name;
    };
    

    看似是只允许通过 getName() 方法获取name属性,但其实仍然可以通过给实例的 _name 属性赋值来更改该属性值。

    let user = new Person("JiaJia")
    console.log(user.getName()); // "JiaJia"
    user._name = "Dlph";
    console.log(user.getName()); // "Dlph"
    

    在ES5中,可以通过下面这种模式创建一个对象接近真正的私有数据。

    var Person = (function() {
        var privateData = {},
            privateId = 0;
        
        function Person(name) {
            Object.defineProperty(this, "_id", { value: privateId++ });
    
            privateData[this._id] = {
                name: name
            };
        }
    
        Person.prototype.getName = function() {
            return privateData[this._id].name;
        }
    
        return Person;
    }());
    
    let user1 = new Person("JiaJia");
    console.log(user1); // Person {_id: 0}
    console.log(user1.getName()); // "JiaJia"
    
    var user2 = new Person("Dlph");
    console.log(user2); // Person {_id: 1}
    console.log(user2.getName()); // "Dlph"
    

    上例中的 privateData 和 privateId 变量被隐藏了起来,在外面无法查看及更改。

    该方法最大的问题是,如果不主动管理,由于无法获知对象实例何时被销毁,因此 privateData 中的数据就永远不会消失。而使用 Weak Map 集合就可以解决该问题。

    let Person = (function() {
        let privateData = new WeakMap();
        
        function Person(name) {
            privateData.set(this, { name });
        }
    
        Person.prototype.getName = function() {
            return privateData.get(this).name;
        }
    
        return Person;
    }());
    
    let user1 = new Person("JiaJia");
    console.log(user1); // Person {}
    console.log(user1.getName()); // "JiaJia"
    
    let user2 = new Person("Dlph");
    console.log(user2); // Person {}
    console.log(user2.getName()); // "Dlph"
    
    user1 = null;
    user2 = null;
    

    使用 Weak Map 来存储私有数据,只要对象实例被销毁,相关信息也会被销毁,从而保证了信息的私有性。

    Weak Map 集合的使用方式及使用限制

    当要在 Weak Map 和 普通 Map 集合之间进行选择时,如果只用对象作为集合的键名,那么 Weak Map 是最好的选择。

    相对于 Map 集合而言, Weak Map 集合对用户的可见度更低,其不支持通过 forEach()方法、size属性及clear()方法来管理集合中的元素。

    如果只想使用非对象名作为键名,那么普通的Map集合是唯一的选择。

  • 相关阅读:
    关于vue2.x使用axios以及http-proxy-middleware代理处理跨域的问题
    vue-resource的使用
    从头开始开发一个vue幻灯片组件
    图与例解读Async/Await
    浅谈web缓存
    APICloud框架——总结一下最近开发APP遇到的一些问题 (三)
    编写现代 CSS 代码的 20 个建议
    仿微信联系人列表滑动字母索引
    初来乍到,向各位大牛虚心学习
    转发80端口的脚本
  • 原文地址:https://www.cnblogs.com/Ryukaka/p/7885786.html
Copyright © 2011-2022 走看看