关于symbol的一些总结

464 阅读6分钟
  • 【JS高阶系列】symbol的一些总结

    • 定义: symbol 是一种基本数据类型【 primitive data type 】。Symbol() 函数会返回 symbol 类型的值【 typeof Symbol() === 'symbol' 】,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册【symbol.for()、symbol.keyFor()】,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。

    • 语法:Symbol([description]) 参数:description 可选的,字符串类型。对 symbol 的描述,可用于调试但不是访问 symbol 本身。

    • 目的:每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型【【【仅有的】】】 目的。

        const symbol1 = Symbol();
        const symbol2 = Symbol(42);
        const symbol3 = Symbol('foo');
    
        console.log(typeof symbol1);
        // Expected output: "symbol"
    
        console.log(symbol2 === 42);
        // Expected output: false
    
        console.log(symbol3.toString());
        // Expected output: "Symbol(foo)"
    
        console.log(Symbol('foo') === Symbol('foo'));
        // Expected output: false
    
    • Static methods 静态方法:

      • Symbol.for()     Searches for existing Symbols with the given key and returns it if found. Otherwise a new Symbol gets created in the global Symbol registry with key 【 在全局范围内查找给定key的Symbol,如果存在的话,将他返回,如果不存在,在全局注册一个给定key的Symbol 】【return Symbol】
          console.log(Symbol.for('bar') === Symbol.for('bar'));
          // Expected output: true
      
          console.log(Symbol('bar') === Symbol('bar'));
          // Expected output: false
      
          const symbol1 = Symbol.for('foo');
      
          console.log(symbol1.toString());
          // Expected output: "Symbol(foo)"
      
      • Symbol.keyFor()     The Symbol.keyFor() static method retrieves a shared symbol key from the global symbol registry for the given symbol.【 Symbol.keyFor(sym) 方法用来获取全局 symbol 注册表中与某个 symbol 关联的键 】【return key of Symbol】
          // 创建一个全局 Symbol
          var globalSym = Symbol.for("foo");
          Symbol.keyFor(globalSym); // "foo"
      
          var localSym = Symbol();
          Symbol.keyFor(localSym); // undefined,
      
          // 以下 Symbol 不是保存在全局 Symbol 注册表中
          Symbol.keyFor(Symbol.iterator); // undefined
      
    • Static properties 静态属性:

      • Symbol.asyncIterator     A method that returns the default AsyncIterator for an object. Used by for await...of.【 一个对象的异步迭代器,会被await...of调用 】
          const delayedResponses = {
          delays: [500, 1300, 3500],
      
          wait(delay) {
              return new Promise((resolve) => {
              setTimeout(resolve, delay);
              });
          },
      
          async *[Symbol.asyncIterator]() {
              for (const delay of this.delays) {
              await this.wait(delay);
              yield `Delayed response for ${delay} milliseconds`;
              }
          },
          };
      
          (async () => {
          for await (const response of delayedResponses) {
              console.log(response);
          }
          })();
      
          // Expected output: "Delayed response for 500 milliseconds"
          // Expected output: "Delayed response for 1300 milliseconds"
          // Expected output: "Delayed response for 3500 milliseconds"
      
      • Symbol.hasInstance    A method determining if a constructor object recognizes an object as its instance. Used by instanceof.【 一个方法,用于确定一个构造器对象识别的对象是否为它的实例的方法,会被instanceof调用 】
          class Array1 {
              static [Symbol.hasInstance](instance) {
                  return Array.isArray(instance);
              }
          }
          console.log([] instanceof Array1);
          // Expected output: true
      
      • Symbol.isConcatSpreadable    A Boolean value indicating if an object should be flattened to its array elements. Used by Array.prototype.concat().【 一个布尔值,表明一个对象是否应该 flattened 为它的数组元素,Array.prototype.concat()方法的参数时是否展开其数组元素。会被Array.prototype.concat()调用 】
          const alpha = ['a', 'b', 'c'];
          const numeric = [1, 2, 3];
          let alphaNumeric = alpha.concat(numeric);
      
          console.log(alphaNumeric);
          // Expected output: Array ["a", "b", "c", 1, 2, 3]
      
          // 此处设置为false,数组将不在展开
          numeric[Symbol.isConcatSpreadable] = false;
          alphaNumeric = alpha.concat(numeric);
      
          console.log(alphaNumeric);
          // Expected output: Array ["a", "b", "c", Array [1, 2, 3]]
      
      • Symbol.iterator    A method returning the default iterator for an object.【 一个方法,返回一个对象的默认迭代元素。会被for...of调用 】
          const iterable1 = {};
          iterable1[Symbol.iterator] = function* () {
              yield 1;
              yield 2;
              yield 3;
          };
          console.log([...iterable1]);
          // Expected output: Array [1, 2, 3]
      
      • Symbol.match    A method that matches against a string, also used to determine if an object may be used as a regular expression.【 一个用于对字符串进行匹配的方法,也用于确定一个对象是否可以作为正则表达式使用。会被String.prototype.match()调用 】
          const regexp1 = /foo/;
          // console.log('/foo/'.startsWith(regexp1));
          // Expected output (Chrome): Error: First argument to String.prototype.startsWith must not be a regular expression
          // Expected output (Firefox): Error: Invalid type: first can't be a Regular Expression
          // Expected output (Safari): Error: Argument to String.prototype.startsWith cannot be a RegExp
          regexp1[Symbol.match] = false; //当为false时,说明匹配源是正则表达式;当true时,说明匹配模式时字符串
          console.log('/foo/'.startsWith(regexp1));
          // Expected output: true
          console.log('/baz/'.endsWith(regexp1));
          // Expected output: false
      
      • Symbol.matchAll    A method that returns an iterator, that yields matches of the regular expression against a string.【 一个用于对字符串进行匹配的方法,返回一个迭代器。会被String.prototype.matchAll()调用 】
          const re = /[0-9]+/g;
          const str = '2016-01-02|2019-03-07';
          const result = re[Symbol.matchAll](str);
          console.log(Array.from(result, (x) => x[0]));
          // Expected output: Array ["2016", "01", "02", "2019", "03", "07"]
      
      • Symbol.replace    A method that replaces matched substrings of a string.【 一个方法,用一个字符串替换匹配到的字符串。会被String.prototype.replace()调用 】
          class Replace1 {
              constructor(value) {
                  this.value = value;
              }
              [Symbol.replace](string) {
                  return `s/${string}/${this.value}/g`;
              }
          }
          console.log('foo'.replace(new Replace1('bar')));
          // Expected output: "s/foo/bar/g"
      
      • Symbol.search    A method that returns the index within a string that matches the regular expression.【 一个方法,返回一个被匹配到的字符的index。会被String.prototype.search()调用 】
          class Search1 {
              constructor(value) {
                  this.value = value;
              }
              [Symbol.search](string) {
                  return string.indexOf(this.value);
              }
          }
          console.log('foobar'.search(new Search1('bar')));
          // Expected output: 3
      
      • Symbol.split    A method that splits a string at the indices that match a regular expression.【 一个方法,分割一个字符串用一个正则表达式在对应的匹配点。会被String.prototype.split()调用 】
          class Split1 {
              constructor(value) {
                  this.value = value;
              }
              [Symbol.split](string) {
                  const index = string.indexOf(this.value);
                  return `${this.value}${string.substr(0, index)}/${string.substr(
                  index + this.value.length,
                  )}`;
              }
          }
          console.log('foobar'.split(new Split1('foo')));
          // Expected output: "foo/bar"
      
      • Symbol.species    A constructor function that is used to create derived objects.【 一个用于创建派生对象的构造器函数 】
          class Array1 extends Array {
              static get [Symbol.species]() {
                  return Array;
              }
          }
          const a = new Array1(1, 2, 3);
          const mapped = a.map((x) => x * x);
          console.log(mapped instanceof Array1);
          // Expected output: false
          console.log(mapped instanceof Array);
          // Expected output: true
      
      • Symbol.toPrimitive    A method converting an object to a primitive value.【 一个方法将一个对象转化为一个原生类型的值 】
          const object1 = {
              [Symbol.toPrimitive](hint) {
                  if (hint === 'number') {
                  return 42;
                  }
                  return null;
              },
          };
          // Expected output: 42
      
      • Symbol.unscopables    An object value of whose own and inherited property names are excluded from the with environment bindings of the associated object.【 拥有和继承属性名的一个对象的值被排除在与环境绑定的相关对象外。 】
          const object1 = {
              property1: 42,
          };
      
          object1[Symbol.unscopables] = {
              property1: true,
          };
          with (object1) {
              console.log(property1);
              // Expected output: Error: property1 is not defined
          }
      
      • Symbol.toStringTag    A string value used for the default description of an object.【 用于对象的默认描述的字符串值。被 Object.prototype.toString() 使用。】【给对象增加Symbol.toStringTag属性,就可以自定义类型!并且通过Object.prototype.toString.call()获取出来。】
          class ValidatorClass {
              get [Symbol.toStringTag]() {
                  return 'Validator';
              }
          }
          console.log(Object.prototype.toString.call(new ValidatorClass()));
          // Expected output: "[object Validator]"
      
          const obj = {};
          Object.defineProperty(obj, Symbol.toStringTag, { value: 'CustomObject' }); // "[object CustomObject]"
      
          // 给对象增加Symbol.toStringTag属性,就可以自定义类型!并且通过Object.prototype.toString.call()获取出来。
          const a = {[Symbol.toStringTag]: 'Foo'x:111}
          console.log(Object.prototype.toString.call(a)) //[object Foo]
      
    • instance properties 实例属性:【 These properties are defined on Symbol.prototype and shared by all Symbol instances. 】【在原型对象上,可共享给实例】

      • Symbol.prototype.constructor    The constructor function that created the instance object. For Symbol instances, the initial value is the Symbol constructor.【创建实例对象的构造函数,对symbol的实例来说,初始值就是Symbol的构造函数】
          const a = Symbol("xxx");
      
      • Symbol.prototype.description    A read-only string containing the description of the Symbol.【一个只读的字符串,意为对该 Symbol 对象的描述】
          console.log(Symbol('desc').description);
          // Expected output: "desc"
          console.log(Symbol.iterator.description);
          // Expected output: "Symbol.iterator"
          console.log(Symbol.for('foo').description);
          // Expected output: "foo"
          console.log(`${Symbol('foo').description}bar`);
          // Expected output: "foobar"
      
          // is equal
          Symbol.keyFor(Symbol('desc'))
      
    • instance methods 实例方法:【 These properties are defined on Symbol.prototype and shared by all Symbol instances. 】【在原型对象上,可共享给实例】

      • Symbol.prototype.toString()    Returns a string containing the description of the Symbol. Overrides the Object.prototype.toString() method. The toString() method of Symbol.【返回一个包含symbol描述其的字符串,重写了 Object.prototype.toString方法】
          console.log(Symbol('desc').toString());
          // Expected output: "Symbol(desc)"
          console.log(Symbol.iterator.toString());
          // Expected output: "Symbol(Symbol.iterator)
          console.log(Symbol.for('foo').toString());
          // Expected output: "Symbol(foo)"
          console.log(Symbol('foo') + 'bar');
          // Expected output: Error: Can't convert symbol to string
      
      • Symbol.prototype.valueOf()    Returns the Symbol. Overrides the Object.prototype.valueOf() method. The valueOf() method of Symbol.【返回一个Symbol的值,重写了 Object.prototype.valueOf方法】
          const symbol1 = Symbol('foo');
          console.log(typeof Object(symbol1));
          // Expected output: "object"
          console.log(typeof Object(symbol1).valueOf());
          // Expected output: "symbol"
          console.log(typeof symbol1.valueOf());
          // Expected output: "symbol"
          console.log(typeof symbol1);
          // Expected output: "symbol"
      
  • symbol 一些常见的知识

    • 为什么symbol 不支持 new Symbol? symbol 是一种基础数据类型,且没有对应的symbol的包装函数 【这里需要注意,原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持,原始类型中,其他的包装函数:new Number、new String、new Boolean 因为遗留原因仍可被创建】;

    • 怎样才能创建 Symbol 包装器对象?

        const a = Symbol()
        const b = Object(a)
        typeof a // 'symbol'
        typeof b // 'object'
    
    • symbol 是不可枚举的,在常规的for....in、Object.keys、JSON.stringify等中,是不能拿到symbol键的 由于键是不可枚举的,所以这也起到了很好的数据保护作用,通过将要保护的数据挂载在symbol键上,达到外部枚举取不到的效果
        const a = Symbol()
        const p = {x:1, y:2, [a]:"666"}
        for (i in p){
            console.log(i) // x 、y
        }
        Object.keys(p); //["x","y"]
        JSON.stringify(p) //'{"x":1,"y":2}'
    
    • Symbol 不可枚举,有没有其他途径获取symbol?
        var a = {name:'张三', age:20, [Symbol('tel')]: 133}
    
        // 使用Object.getOwnPropertySymbols
        Object.getOwnPropertySymbols(a) // [Symbol(tel)]
    
        // 使用Reflect.ownKeys反射API
        Reflect.ownKeys(a) // ['name', 'age', Symbol(tel)]
    
    • Symbol([description]) 内的参数description只是 新构建的Symbol的一种描述,不提供也可以,此字符串并不是Symbol 本身
        Symbol('box') === 'box' //false
    
    • 怎样创建可全局共享的symbol? 默认状态下,创建的symbol() 创建的symbol是不可共享的!!! 需要创建共享跨文件、跨窗口甚至跨域的可共享的symbol,需要用到对应的全局apiSymbol.for()、Symbol.keyFor(),注意:Symbol.keyFor是针对全局的,对于Symbol()创建的局部Symbol值无效
        // 不可共享
        const customSymbol = Symbol()
    
        // 全局 Symbol, 可全局共享
        var globalSym = Symbol.for("foo");
        Symbol.keyFor(globalSym); // "foo"
    
        // Symbol.keyFor是针对全局的,对于`Symbol()`创建的局部Symbol值无效
        Symbol.keyFor(Symbol('desc')) // undefined
    
  • symbol 常见的应用场景

    犹如官方所说,提供唯一的值是他的【【【仅有的】】】 目的

    • 消除魔术字符串【魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。】
        //修改前 【字符串Triangle多次出现,强耦合】
        function getArea(shape, options) {
            let area = 0;
            switch (shape) {
                case 'Triangle':
                area = .5 * options.width * options.height;
                break;
                /* ... more code ... */
            }
            return area;
        }
        getArea('Triangle', { width: 100, height: 100 });
    
        //修改后 【清除字符串强耦合】
        const shapeType = {
            triangle: Symbol()
        };
        function getArea(shape, options) {
            let area = 0;
            switch (shape) {
                case shapeType.triangle:
                area = .5 * options.width * options.height;
                break;
            }
            return area;
        }
        getArea(shapeType.triangle, { width: 100, height: 100 });
    
    • 模块的 Singleton 模式【模块的单例模式,调用一个类,任何时候返回的都是同一个实例】
        //修改前 【mod.js 内创建挂载全局的A的实例,单例,但是全局global可修改】
        // mod.js
        function A() {
            this.foo = 'hello';
        }
        if (!global._foo) {
            global._foo = new A();
        }
        module.exports = global._foo;
        // use.js
        const a = require('./mod.js');
        console.log(a.foo);
    
    
        //修改后 【清除字符串强耦合】
        // mod.js
        /**
         * 这里面为什么不使用 `Symbol.for('foo')`,因为Symbol.for是全局的,在外部文件内仍然可以访问到,可以修改
         * 但是换成 `Symbol('foo')`就是非全局的了,Symbol('foo')创建的symbol是局部的,不会溢出mod.js文件,在外部不可访问,不可修改,达到真正的具有保护效果的单例
         */
        // const FOO_KEY = Symbol.for('foo'); 
        const FOO_KEY = Symbol('foo');
        function A() {
            this.foo = 'hello';
        }
        if (!global[FOO_KEY]) {
            global[FOO_KEY] = new A();
        }
        module.exports = global[FOO_KEY];
    
        // use.js
        const a = require('./mod.js');
        console.log(a.foo);