第六章 符号

85 阅读4分钟

普通符号

符号是ES6中新增的一个数据类型,它通过调用Symbol("符号描述")函数进行创建

 const sym1 = Symbol();
 const sym2 = Symbol("这是一个符号");

符号的特点

  1. 符号没有字面量

  2. symbol是一个原始类型,使用typeof得到的是"symbol"

  3. 每次调用Symbol函数得到的符号都是一个全新的符号,它不会与任何其他符号相等

     const sym1 = Symbol();
     const sym2 = Symbol();
     ​
     console.log(sym1 === sym2);     // false
    
  4. 符号可以作为对象的属性名存在,这种属性称为符号属性

     const sym = Symbol();
     ​
     const obj = {
         [sym]: "abc"
     };
     ​
     console.log(obj[sym]);
    
  5. 符号属性是不可枚举的,for in循环遍历不到它,Object.keys()也获取不到符号属性

    尽管Object.getOwnPropertyNames()可以得到无法枚举的属性,但它也无法得到符号属性

    ES6新增了Object.getOwnPropertySymbols()方法,利用该方法可以得到对象的所有符号属性

  6. 符号无法隐式转换,因此符号无法参与到算术运算、字符串拼接或其他会发生隐式转换的运算中,不过符号可以显式转换为字符串,通过String()函数即可显式转换

     console.log(String(Symbol()));      // "Symbol()"
    

符号的应用

利用符号的特点,可以为对象创建一个外部难以访问的成员

 const obj = (()=>{
     const sym = Symbol();
     return {
         [sym]: 1,
         print(){
             console.log(this[sym]);
         }
     }
 })();
 ​
 const sym = Symbol();
 console.log(obj[sym]);      // undefined
 obj.print();                // 1

外部要想访问符号属性,只能通过Object.getOwnPropertySymbols()来辅助进行访问

 const syms = Object.getOwnPropertySymbols(obj);
 ​
 console.log(obj[syms[0]]);      // 1

同理,也可以为类创建一个外部难以访问的成员

 const Hero = (()=>{
     const sym = Symbol();
     return class {
         constructor(){}
         
         [sym](){
             // ...
         }
     }
 })();

共享符号

只要符号描述相同,就可以得到同一个符号

使用Symbol.for("符号描述")即可创建或获取共享符号

 const sym1 = Symbol.for("abc");
 const sym2 = Symbol.for("abc");
 ​
 console.log(sym1 === sym2);

共享符号的实现

 const SymbolFor = (()=>{
     const obj = {};
     return function(description){
         if(!obj[description]){
             obj[description] = Symbol(description);
         }
         return obj[description];
     }
 })();

知名符号

知名符号是一些具有特殊含义的共享符号,它通过Symbol的静态属性进行获取

Symbol.hasInstance

该知名符号会影响instanceof操作符的判定

 function Func(){
     
 }
 ​
 const obj = {};
 ​
 obj instanceof Func
 // 实际上是
 Func[Symbol.hasInstance](obj);

可以通过修改Func的Symbol.hasInstance属性来控制instanceof关键字对Func使用的效果

 Object.defineProperty(Func, Symbol.hasInstance, {
     value(){
         console.log("abc");
         return true;
     }
 });
 ​
 console.log(obj instanceof Func);   // "abc"  true

注意:Symbol.hasInstance只能通过Object.definedProperty()进行修改

Symbol.isConcatSpreadable

该知名符号会影响数组的concat方法

默认情况下,数组在concat时会被分割:

 const arr1 = [1, 2, 3];
 const arr2 = [4, 5, 6];
 const arr3 = arr1.concat(arr2);
 // arr3 => [1, 2, 3, 4, 5, 6]

当数组的该知名符号设置为false时,数组就不会在concat时被分割:

 const arr1 = [1, 2, 3];
 const arr2 = [4, 5, 6];
 arr2[Symbol.isConcatSpreadable] = false;
 const arr3 = arr1.concat(arr2);
 // arr2 => [1, 2, 3, [4, 5, 6]]

该符号属性不仅可以使用在数组中,也可以使用在类数组中

Symbol.toPrimitive

该知名符号会影响引用值类型转换(包括显式转换和隐式转换)为原始值的结果

如果没有配置该知名符号属性,则类型转换时会按照传统的使用valueof或toString返回值的方式进行转换

如果配置了该知名符号属性,则类型转换时会直接将该知名符号属性的返回值作为类型转换的结果,而不会按照传统的方式进行转换

手动配置时要求该知名符号必须设置为一个函数,并且该函数的返回值必须是原始值

 var obj = {
     toString() {
         return 3;
     },
     valueOf() {
         return 2;
     },
     [Symbol.toPrimitive]() {
         return 1;
     }
 };
 ​
 console.log(obj + 1);   // 2

该知名符号函数可以接收一个字符串参数,其代表着期望引用值转换为哪种原始类型

  • default:无法确定要转为什么类型
  • number:期望转为数字类型
  • string:期望转为字符串类型
 var obj = {
     [Symbol.toPrimitive](hint) {
         console.log(hint);
     }
 };
 ​
 obj + 1;            // "default"
 obj * 1;            // "number"
 String(obj);        // "string"

Symbol.toStringTag

该知名符号会影响Object.prototype.toString的返回值

通过自定义构造函数创建的对象,其使用Object.prototype.toString时返回的是"[object Object]",但这是可以修改的

 class Person{
     [Symbol.toStringTag] = "Person"
 }
 ​
 const person = new Person();
 ​
 console.log(Object.prototype.toString.call(person));        // "[object Person]"