• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Symbol 类型

    在使用别人提供的对象时,在拿到这个对象,然后想往里面添加新的属性名,为了防止和别人的属性名重复,这样是会发生冲突的,对象的属性名必须要求是独一无二的,因此,ES6 中加入了 Symbol 数据类型,确保对象的属性名独一无二Symbol 实际上是引入的一种原始数据类型

    // ES6的symbol
    let sym = Symbol("addr");
    console.log(sym);
    console.log(typeof sym);
    

    Symbol函数不是一个构造函数,前面不能用new操作符,所以Symbol类型的值也不是对象,不能添加任何属性,只是类似于字符型的数据类型,强加new操作符会报错!


    Symbol 值是唯一的,这是 Symbol 变量最大的特点。通过同样的方式创建的两个变量,即使参数也一样,这两个 Symbol 变量也是不相等的。

    let x = Symbol();
    let y = Symbol();
    
    console.info(x === x);  // true
    console.info(x === y);  // false
    


    symbols 重要的用途,就是用作对象的 key。

    const obj = {};
    const sym = Symbol();
    obj[sym] = 'foo';
    obj.bar = 'bar';
    
    console.log(obj);  // { bar: 'bar' }
    console.log(sym in obj);   // true
    console.log(obj[sym]);   // foo
    console.log(Object.keys(obj));   // ['bar']
    

    我们注意到使用Object.keys()并没有返回 symbols,这是为了向后兼容性的考虑。老代码不兼容 symbols,因此古老的Object.keys()不应该返回 symbols。


    Symbol函数的参数

    Symbol 函数还可以接受一个字符参数,来对产生的 Symbol 值进行描述,方便我们区分不同的 Symbol值

    let symName = Symbol('test');
    let symAddr = Symbol('test');
    console.log('symName',symName);
    console.log('symAddr',symAddr);
    console.log('symName == symAddr?',symName == symAddr);
    let sAddr = Symbol('symAddr');
    console.log('sAddr == symAddr?',sAddr == symAddr);
    

    如果创建 Symbol 值时传入的是对象,那么会先调用对象的toString方法得到一个字符串,再将结果字符串作为参数生成一个 Symbol 值。所以,Symbol 函数的参数只能是字符串。

    const obj = {
      a: "a",
      b: "b",
      toString() {
        return "xx";
      },
    };
    console.info(Symbol(obj)); // Symbol(xx)
    

    如果创建 Symbol 值时传入的是对象,没有定义 toString 方法:

    const obj = {
      a: "a",
      b: "b",
    };
    
    console.info(Symbol(obj)); // Symbol([object Object])
    


    Symbol 值不可以进行运算

    Symbol 是一种数据类型,但它与 Number 以及 String 类型不同,不能参与运算,Symbol 值可以转化为字符串或布尔值,但是不能转为数值。

    let sym1 = Symbol('sym1');
    let sym2 = Symbol('sym2');
    //转为字符串
    console.log(String(sym1));
    //转为布尔值
    console.log(Boolean(sym2));
    // 直接进行运算
    console.log('sym1+sym2=?',sym1+sym2);
    


    Symbol 作为属性

    Symbol 就是用于属性去创造的,下面是几种 Symbol 作为属性的写法

    //写法一:
    let obj = {}; //创建一个对象
     let sym = Symbol(); 
    obj[sym] = '湖南省长沙市';
    //取属性值
    console.log(obj[sym]);
    
    // 写法二
    let sym = Symbol(); 
    let obj = {
        [sym]:'安徽省芜湖市'
    }; 
    console.log(obj[sym]);
    
    //写法三
    let obj = {}
    let sym = Symbol()
    Object.defineProperty(obj,sym,{value:'小陈同学'});
    console.log(obj[sym]);
    

    Symbol 值作为属性名时,获取相应的属性值不能用作点运算符,如果点运算符来给对象的属性赋 Symbol 类型的值,实际上属性名会变成字符串,而不是 Symbol 值,在对象内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号内,否则就是一个字符串。


    getOwnPropertySymbols()

    通常遍历对象,会有for...in, for...ofObject.keys()Object.getOwnPropertyNames()这些方法,但是,这些方法都不能获取到对象的 Symbol 值的属性名。那是不是在外部就没有办法获取 Symbol 值的属性名了呢?办法当然还是有的。

    Object.getOwnPropertySymbols()就可以返回对象中的所有类型为 Symbol 值的属性名,得到一个元素类型为 symbol 的数组。除此之外,还有一个方法Reflect.ownKeys()可以返回包含 Symbol 属性名的所有属性名。

    const obj = {
      a: 1,
      [Symbol()]: 2,
    };
    
    console.info("obj", Object.getOwnPropertyNames(obj));   //  [ 'a' ]
    console.info("obj", Object.getOwnPropertySymbols(obj));  //  [ Symbol() ]
    console.info("obj", Reflect.ownKeys(obj));   //  [ 'a', Symbol() ]
    
    var obj={
      [Symbol('name')]:'like',
      [Symbol('age')]:'18',
      [Symbol()]:'play dou dou',
      sex:'male'
    }
    
    obj.sex  // male
    
    //获取不到
    obj[Symbol('name')]  //undefined
    obj[Symbol('age')]  //undefined
    obj[Symbol()]  //undefined
    
    //for 检测不到
    for(var i in obj){
      console.log(obj[i])
    }
    // 输出结果只有:male
    
    let name = Symbol('name');
    let age = Symbol('age');
    let hobby = Symbol('hobby');
    
    var obj={
      [name]:'like',
      [age]:'18',
      [hobby]:'play dou dou',
      sex:'male'
    }
    
    obj[name]  //like
    obj[age]  //18
    
    //仍然遍历不到
    for(var i in obj){
      console.log(obj[i])
    }
    // 输出结果只有:male
    
    //Object.getOwnPropertySymbols(obj) 可以遍历到
    let symbols = Object.getOwnPropertySymbols(obj)
    for(var i in symbols){
      console.log(obj[symbols[i]])
    }
    /*遍历结果:
    like
    18
    play dou dou
    */
    


    Symbol.for()、Symbol.keyFor()

    Symbol 类型的变量除了自身,不会等于其他任何变量,但是依然存在特例。区别于通过Symbol()函数创建 Symbol 变量,我们还可以通过Symbol.for()方法来创建 Symbol 变量,该方法可以传入参数,而且在传入参数相同的时候,创建的 Symbol 也是相等的。

    const a = Symbol.for("aa");
    const b = Symbol.for("aa");
    console.info("a===b", a === b);  // a===b true
    console.info("type", typeof a);  // type symbol
    

    Symbol 类型的 a 和 b ,通过===运算可以得到true。这是为什么呢?

    因为Symbol.for()其实是带有类似重用机制的,具体的说,就是通过Symbol.for()创建变量时,传入的参数(假设为 x)会作为 Symbol 变量的 key ,然后到全局中搜索,是否已经有相同 key 的 Symbol 变量,如果存在,则直接返回这个 Symbol 变量。如果没有,才会创建一个 key 为传入参数 x 的 Symbol 变量,并将这个变量写到全局,供下次创建时被搜索。

    通过Symbol.for()创建的 Symbol 变量,传入的参数是否相等决定得到的 Symbol 变量是否相等。

    分析上面的代码,通过Symbol.for("aa")创建变量并赋值给 a 的时候,会在全局检索是否有 key 为'aa'的 Symbol 变量,这里显然是没有的,所有得到一个全新的 Symbol 变量,其带有的 key 是'aa',并且变量被写到全局。然后执行const b = Symbol.for("aa");时,Symbol.for('aa')会找到之前的 key 为'aa'的 Symbol 变量,并赋值给 b ,所以,a 和 b 实际上是同一个 Symbol 变量。此时 Symbol.for("aa")不管执行多少次,得到的都是同一个 Symbol 变量。

    既然通过Symbol.for()创建的 Symbol 变量的 key 这么重要,那我们怎么获取到这个 key 呢,那就要Symbol.keyFor()方法了,该函数会返回一个已经写到全局的 Symbol 变量的 key 值。这样获取到 Symbol 变量的 key,就可以创建一个和原 Symbol 变量相等的变量了。


    内置的 Symbol 值

    • Symbol.hasInstance:对象的 Symbol.hasInstance 属性指向一个内部方法,当其他对象使用 instance 运算符,判断是否为该对象的实例时,会调用这个方法。
    • Symbol.isConcatSpreadable:布尔值,表示该对象用于 Array.prototype.concat()时,是否可以展开。
    • Symbol.species:对象的 Symbol.species 属性,指向一个构造函数。创建衍生对象时,会使用该属性定义 Symbol.species 属性要采用 get 取值器。
    • Symbol.match:指向一个函数。当执行 str.match(myObject)时,如果该属性存在,会调用他,返回该方法的返回值。
    • Symbol.replace:指向一个方法,当该对象被 String.prototype.replace 方法调用时,会返回该方法的返回值。
    • Symbol.search:指向一个方法,当该对象被 String.prototype.search 方法调用时,会返回该方法的返回值。
    • Symbol.split:指向一个方法,当该对象被 String.prototype.split 方法调用时,会返回该方法的返回值。
    • Symbol.iterator:对象的 Symbol.iterator 属性,指向该对象的默认遍历器方法。
    • Symbol.toPrimitive:指向一个方法,当该对象被转化为原始类型的值时,会返回该方法的返回值。被调用时,会接受一个字符串参数,表示当前运算的模式:Number,String,Default。
    • Symbol.toStringTag:指向一个方法,当该对象被 String.prototype.toString 方法调用时,可以用来定制[object xxx]中 object 后面的那个字符串。
    • Symbol.unscopables:指向一个对象,该对象指定了使用 with 关键字时,那些属性会被 with 环境排除(with 语句的作用是将代码的作用域设置到一个特定的作用域中)。