JS数据类型 之 Symbol详解

1、Symbol概述

ES6 引入的一种新的原始数据类型Symbol,表示独一无二的值。

它属于JavaScript语言的原生数据类型之一,其他数据类型是:undefined、null、Boolean、String、Number、Bigint、Object。

使用场景:

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。

比如,你使用了一个他人提供的对象,你想为这个对象添加新的方法(mixin模式),这个时候新方法的名字就有可能和现有的方法重名,这个时候就可以使用Symbol,来防止你新追加的方法重名。


2、Symbol的特性

let s = Symbol();
console.log(typeof s); // symbol

上述代码,s就是一个独一无二的值。

注意:Symbol()函数之前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,而不是对象。另外,由于Symbol值不是对象,所以也不能添加属性。基本上它就是一种类似字符串的数据类型


let s1 = Symbol('s1');
let s2 = Symbol('s2');
console.log(s1, s2); // Symbol(s1) Symbol(s2)
console.log(s1.toString(), s2.toString()); // "Symbol(s1)" "Symbol(s2)"

Symbol()函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。

这主要是为了在控制台显示,或者转为字符串的时候,比较容易区分。

注意:Symbol()函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。


let obj = {
    toString: function() {
        return 'abc';
    }
}

console.log(Symbol(obj)); // abc

let s = Symbol('mySymbol');

// console.log(s + '123'); // Uncaught TypeError: Cannot convert a Symbol value to a string
console.log(String(s), s.toString()); // 'Symbol(mySymbol)' 'Symbol(mySymbol)'
console.log(Boolean(s)); // true
console.log(Number(s)); // Uncaught TypeError: Cannot convert a Symbol value to a number

当Symbol函数的参数是一个对象时,会自动调用对象中的toString方法,转成字符串之后,再把这个字符串作为参数去执行。

Symbol可以转成字符串、布尔值,不能转成数字,也不能和其他类型的值进行运算。


3、Symbol.prototype.description

ES2019提供了一个Symbol值的实例属性description,可以直接返回Symbol值的描述。

let s = Symbol('my symbol')

console.log(s.description); // my symbol

4、Symbol作为对象属性名

由于每一个 Symbol 值都是不相等的,这意味着只要 Symbol 值作为标识符,用于对象的属性名,就能保证不会出现同名的属性。

let mySymbol = Symbol('mySymbol');

// 第一种写法
let a = {};
a[mySymbol] = 'hello Symbol';

// 第二种写法
let b = {
    [mySymbol]: 'hello Symbol'
}

// 第三种写法
let c = {};
Object.defineProperty(c, mySymbol, { value: 'hello Symbol' });

console.log(a, b, c);

注意:Symbol 值作为对象属性名时,不能用点运算符。

因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值。

还有一点需要注意,Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。


5、Symbol作为常量

Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。

const log = {};

log.levels = {
    DEBUG: Symbol('debug'),
    INFO: Symbol('info'),
    WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');

6、消除魔术字符串

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

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 }); // 魔术字符串

例如上述代码,Triangle就是一个魔术字符串,它会多次出现在more code部分,与代码形成强耦合。

常用的消除魔术字符串的方法,就是把它写成一个变量。

const shapeType = {
  triangle: 'Triangle'
};

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 });

如果仔细分析,可以发现shapeType.triangle等于哪个值并不重要,只要确保不会跟其他shapeType属性的值冲突即可。因此,这里就很适合改用 Symbol 值。

const shapeType = {
  triangle: Symbol()
};

7、Symbol常用方法

Symbol.for(key)

  • 作用:使用给定的key搜索现有的symbol【key表示symbol中的description】
  • 返回值:如果找到,返回该symbol;否则将创建一个新的symbol,key作为description,添加到注册表中并返回。
  • 简述:用description找Symbol,找到就返回,找不到就创建一个新的Symbol。

Symbol.keyFor(symbol)

  • 作用:从symbol注册表中,返回指定symbol的description,没有则返回undefined。
  • 返回值:如果有description,返回;否则返回undefined。

注意:

  • 如果使用Symbol()定义的symbol,则不会添加到注册表中;使用Symbol.for()定义的symbol则会添加到注册表中。
  • Symbol.for(key)和Symbol.keyFor(sym)都是在Symbol注册表中进行查找。不会找到Symbol()定义的symbol

8、遍历对象中的Symbol

let symbol = Symbol("sym");
let obj = {
    name: "symbol_name",
    [symbol]: "唯一性"
};

// 访问对象的基本属性
for (const key in obj) {
    console.log(key); // name
}
for (const key of Object.keys(obj)) {
    console.log(key); // name
}

// 访问对象中的私有属性
for (const key of Object.getOwnPropertySymbols(obj)) {
    console.log(key); // Symbol(sym)
}

for (const key of Reflect.ownKeys(obj)) {
    // 访问对象的所有属性
    console.log(key); // name 、Symbol(sym)
}

注意:

  • 对象的属性要使用变量值,必须使用[变量名]
  • Symbol类型作为对象的私有属性,通过for/in、for/of无法遍历,必须使用Object.getOwnPropertySymbols(对象)或Reflect.ownKeys(对象)进行遍历

此外,Symbol还有11个ES6内置的 Symbol 值,指向语言内部使用的方法,有兴趣的话请前往下述学习参考的第一个链接进行学习。

学习参考:

posted @ 2022-09-23 09:25  笔下洛璃  阅读(685)  评论(0编辑  收藏  举报