• 第十四节:ES6之Symbol、Set和WeakSet、Map和WeakMap详解


    一. Symbol详解

    1. 说明

         ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它是 JavaScript 语言的第七种数据类型, 前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。 Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的;

         PS: ES10中,Symbol还新增了一个description描述符

    {
        const s1 = Symbol();
        const s2 = Symbol();
        const s3 = Symbol("ypf");
        const s4 = Symbol("ypf");
        console.log(s1 === s2); //false
        console.log(s3 === s4); //false
    
        console.log(s3.description); //ypf
    }

    2. Symbol作为key

      (1). 定义key

      (2). 新增属性

      (3). Object.defineProperty方式定义

      (4). 使用Symbol作为key的属性名,在遍历/Object.keys等中是获取不到这些Symbol值

    {
        const s1 = Symbol();
        const s2 = Symbol();
        const s3 = Symbol();
        const s4 = Symbol();
        // 定义属性
        let obj = {
            [s1]: "ypf1",
            [s2]: "ypf2",
        };
        // 新增属性
        obj[s3] = "ypf3";
        // defineProperty的方式定义属性
        Object.defineProperty(obj, s4, {
            enumerable: true,
            configurable: true,
            writable: true,
            value: "ypf4",
        });
        //获取属性
        console.log(obj[s1], obj[s2], obj[s3], obj[s4]); //ypf1 ypf2 ypf3 ypf4
        console.log(Object.keys(obj)); //获取不到 []
        console.log(Object.getOwnPropertyNames(obj)); //获取不到 []
        console.log(Object.getOwnPropertySymbols(obj)); //能获取 //[ Symbol(), Symbol(), Symbol(), Symbol() ]
        const sKeys = Object.getOwnPropertySymbols(obj); //
        for (const sKey of sKeys) {
            console.log(obj[sKey]); //依次输出 ypf1 ypf2 ypf3 ypf4
        }
    }

    3. Symbol.for()/Symbol.keyFor()

    (1). Symbol.for(): 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

    注:Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。

           Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。

    (2). Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。

    注:只能获取Symbol.for("foo");创建的key,不能获取symbol获取的key

    {
        const s1 = Symbol.for("ypf");
        const s2 = Symbol.for("ypf");
        console.log(s1 === s2); //true
    }
    {
        const s1 = Symbol("foo");
        console.log(Symbol.keyFor(s1)); // undefined
    
        const s2 = Symbol.for("foo");
        console.log(Symbol.keyFor(s2)); // foo
    }

    4. 应用场景

        消除魔术字符串:魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。

     代码分享:

    {
        //原始写法:字符串Triangle和Circle就是魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
        function getArea(shape, options) {
            let area = 0;
            switch (shape) {
                case "Triangle": // 魔术字符串
                    area = 0.5 * 0.5;
                    break;
                /* ... more code ... */
            }
            return area;
        }
    
        getArea("Triangle", {  100, height: 100 }); // 魔术字符串
    }
    
    {
        //常用的消除魔术字符串的方法,就是把它写成一个变量。
        const shapeType = {
            triangle: "Triangle",
        };
        function getArea(shape, options) {
            let area = 0;
            switch (shape) {
                case shapeType.triangle:
                    area = 0.5 * 0.5;
                    break;
            }
            return area;
        }
        getArea(shapeType.triangle, {  100, height: 100 });
    }
    
    {
        // 可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用 Symbol 值。
        const shapeType = {
            triangle: Symbol(),
            circle: Symbol(),
        };
    
        function getArea(shape) {
            let area = 0;
            switch (shape) {
                case shapeType.triangle:
                    area = 0.5 * 0.5;
                    break;
                case shapeType.circle:
                    // ... more code ...
                    break;
            }
            return area;
        }
        console.log(getArea(shapeType.triangle));
    }
    View Code

    二. Set详解

    1. 含义

      Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):

     2. 基本用法

        (1). 通过new来实例化set结构

        (2). size:返回Set中元素的个数;

        (3). add(value):添加某个元素,返回Set对象本身;

        (4). delete(value):从set中删除和这个值相等的元素,返回boolean类型;

        (5). has(value):判断set中是否存在某个元素,返回boolean类型;

        (6). clear():清空set中所有的元素,没有返回值;

        (7). 遍历:

           forEach():使用回调函数遍历每个成员

           for...of:可以直接遍历每个成员

           keys():返回键名的遍历器

           values():返回键值的遍历器

           entries():返回键值对的遍历器

    代码分享:

    {
        //生成Set实例
        const myS1 = new Set();
        let myS2 = new Set([1, 2, 3, 4]);
        // 添加数据
        myS1.add(1);
        myS1.add(2);
        myS1.add(3);
        myS1.add(4);
        myS1.add(5).add(2);
        // size属性
        console.log(myS1.size); //5
        // delete方法
        myS1.delete(3);
        //has方法
        console.log(myS1.has(3)); //false
        //遍历
        {
            //写法1
            myS1.forEach(item => console.log(item)); //1 2 4 5
            //写法2
            for (const item of myS1) {
                console.log(item); //1 2 4 5
            }
            console.log(myS1.keys()); // [Set Iterator] { 1, 2, 4, 5 }
            console.log(myS1.values()); // [Set Iterator] { 1, 2, 4, 5 }
            console.log(myS1.entries()); //[Set Entries] { [ 1, 1 ], [ 2, 2 ], [ 4, 4 ], [ 5, 5 ] }
        }
    }

    3. 用途

        (1). 数组去重: 先将数组放到new Set中,然后通过展开运算符或者Array.from将set转换成普通数组

        (2). 合并去重:将多个数组通过展开运算符放到Set实例中,然后通过展开运算符或者Array.from将set转换成普通数组

        (3). 交集

        (4). 差集

    代码分享:

    // 2.1 数组去重
    {
        const myArray = [10, 20, 30, 30, 20, 50, 40];
    
        // 写法1
        let newArray = [];
        for (const item of myArray) {
            if (!newArray.includes(item)) {
                newArray.push(item);
            }
        }
        console.log(newArray); //[ 10, 20, 30, 50, 40 ]
    
        // 写法2
        const mySet1 = new Set(myArray);
        let newArray2 = [...mySet1];
        console.log(newArray2); //[ 10, 20, 30, 50, 40 ]
    
        // 写法3
        const mySet3 = new Set(myArray);
        let newArray3 = Array.from(mySet3);
        console.log(newArray3); //[ 10, 20, 30, 50, 40 ]
    }
    
    // 2.2 合并去重
    {
        const array1 = [1, 3, 4, 5];
        const array2 = [2, 3, 5, 6];
        let mySet = new Set([...array1, ...array2]);
        let newArray = [...mySet];
        console.log(newArray); //[ 1, 3, 4, 5, 2, 6 ]
    }
    
    // 2.3 交集
    {
        const array1 = [1, 3, 4, 5];
        const array2 = [2, 3, 5, 6];
        let result = new Set(array1.filter(item => array2.includes(item)));
        console.log([...result]);
    }
    // 2.3 差集
    {
        let arr1 = [1, 2, 3, 4];
        let arr2 = [2, 3, 4, 5, 6];
        let s1 = new Set([1, 2, 3, 4]);
        let s2 = new Set([2, 3, 4, 5, 6]);
        let arr3 = new Set(arr1.filter(item => !s2.has(item)));
        let arr4 = new Set(arr2.filter(item => !s1.has(item)));
        console.log(arr3); // Set(1) { 1 }
        console.log(arr4); // Set(2) { 5, 6 }
        console.log([...arr3, ...arr4]); // [ 1, 5, 6 ]
    }
    View Code

    三. WeakSet详解

     1. 含义

         另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。

         那么和Set有什么区别呢?

         区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;

         区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;

    {
        const myWeakSet = new WeakSet();
        let obj = { name: "ypf" };
        myWeakSet.add(obj);
        // 下面报错
        // myWeakSet.add(10); //TypeError: Invalid value used in weak set
    }

    2. 基本用法

        (1).add(value):添加某个元素,返回WeakSet对象本身;

        (2).delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;

        (3).has(value):判断WeakSet中是否存在某个元素,返回boolean类型;

    3. 使用场景  

        WeakSet不能遍历

        因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。

        所以存储到WeakSet中的对象是没办法获取的;

    {
        const personSet = new WeakSet();
        class Person {
            constructor() {
                personSet.add(this);
            }
    
            running() {
                if (!personSet.has(this)) {
                    throw new Error("不能通过非构造方法创建出来的对象调用running方法");
                }
                console.log("running~", this);
            }
        }
        let p = new Person();
        p.running();
        p = null;
        p.running.call({ name: "ypf" });
    }

    四. Map详解

    1. 含义

         ES6新增了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

         也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。

    {
        const obj1 = { name: "ypf1" };
        const obj2 = { name: "ypf2" };
        const myMap1 = new Map();
        myMap1.set(obj1, "aaa");
        myMap1.set(obj2, "bbb");
        myMap1.set("003", "ccc");
        console.log(myMap1);
        // 实例化的时候直接赋值
        const myMap2 = new Map([
            [obj1, "aaa"],
            [obj2, "bbb"],
            ["003", "ccc"],
        ]);
        console.log(myMap2);
    }

    2. 常用方法

         size:返回Map中元素的个数;

         set(key, value):在Map中添加key、value,并且返回整个Map对象;

         get(key):根据key获取Map中的value;

         has(key):判断是否包括某一个key,返回Boolean类型;

         delete(key):根据key删除一个键值对,返回Boolean类型;

         clear():清空所有的元素;

         forEach(callback, [, thisArg]):通过forEach遍历Map;

         Map也可以通过for of进行遍历。

     代码分享:

    {
        const obj1 = { name: "ypf1" };
        const obj2 = { name: "ypf2" };
        const myMap = new Map([
            [obj1, "aaa"],
            [obj2, "bbb"],
            ["003", "ccc"],
        ]);
        myMap.set("001", "ypf1");
        console.log(myMap.size); //4
        console.log(myMap.get("001")); //ypf1
        console.log(myMap.has("001")); //true
        myMap.delete(obj1);
        // 遍历
        console.log("-------------遍历--------------");
        myMap.forEach((item, key) => {
            console.log(key, item);
        });
        for (const item of myMap) {
            console.log(item[0], item[1]);
        }
        for (const [key, value] of myMap) {
            console.log(key, value);
        }
    }

    五. WeakMap详解

    1. 含义

          和Map类型相似的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。

          那么和Map有什么区别呢?

          区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;

          区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;

    2. 常用方法

          set(key, value):在Map中添加key、value,并且返回整个Map对象;

          get(key):根据key获取Map中的value;

          has(key):判断是否包括某一个key,返回Boolean类型;

          delete(key):根据key删除一个键值对,返回Boolean类型;

    {
        const obj = { name: "obj1" };
    
        //区别一: 不能使用基本数据类型
        // weakMap.set(1, "ccc")
    
        // WeakMap和Map的区别二:
        const map = new Map();
        map.set(obj, "aaa");
    
        const weakMap = new WeakMap();
        weakMap.set(obj, "aaa");
    
        // 3.常见方法
        // get方法
        console.log(weakMap.get(obj));
    
        // has方法
        console.log(weakMap.has(obj));
    
        // delete方法
        console.log(weakMap.delete(obj));
        // WeakMap { <items unknown> }
        console.log(weakMap);
    }

    3. 响应式原理中WeakMap的使用

     代码分享:

    {
        // 应用场景(vue3响应式原理)
        const obj1 = {
            name: "why",
            age: 18,
        };
        function obj1NameFn1() {
            console.log("obj1NameFn1被执行");
        }
        function obj1NameFn2() {
            console.log("obj1NameFn2被执行");
        }
        function obj1AgeFn1() {
            console.log("obj1AgeFn1");
        }
        function obj1AgeFn2() {
            console.log("obj1AgeFn2");
        }
        const obj2 = {
            name: "kobe",
            height: 1.88,
            address: "广州市",
        };
        function obj2NameFn1() {
            console.log("obj1NameFn1被执行");
        }
        function obj2NameFn2() {
            console.log("obj1NameFn2被执行");
        }
        // 1.创建WeakMap
        const weakMap = new WeakMap();
    
        // 2.收集依赖结构
        // 2.1.对obj1收集的数据结构
        const obj1Map = new Map();
        obj1Map.set("name", [obj1NameFn1, obj1NameFn2]);
        obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2]);
        weakMap.set(obj1, obj1Map);
    
        // 2.2.对obj2收集的数据结构
        const obj2Map = new Map();
        obj2Map.set("name", [obj2NameFn1, obj2NameFn2]);
        weakMap.set(obj2, obj2Map);
    
        // 3.如果obj1.name发生了改变
        // Proxy/Object.defineProperty
        obj1.name = "james";
        const targetMap = weakMap.get(obj1);
        const fns = targetMap.get("name");
        fns.forEach(item => item());
    }
    View Code

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    【HDU3480】Division-DP+四边形不等式优化+贪心
    【HDU3480】Division-DP+四边形不等式优化+贪心
    【NOI2015T2】软件包管理器-树链剖分维护路径和子树信息
    【NOI2015T2】软件包管理器-树链剖分维护路径和子树信息
    【APIO2011T1】方格染色-并查集+位运算推导
    【APIO2011T1】方格染色-并查集+位运算推导
    【NOI2016T4】区间-线段树+离散化+决策单调性优化
    【NOI2016T4】区间-线段树+离散化+决策单调性优化
    【NOI2010T4】航空管制-拓补排序+贪心
    BZOJ 1753 Who's in the Middle
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/16030113.html
Copyright © 2020-2023  润新知