第十四节: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", { width: 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, { width: 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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

posted @ 2022-03-20 15:25  Yaopengfei  阅读(241)  评论(1编辑  收藏  举报