Symbol 类型

184 阅读4分钟

1、概述

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值,主要为了解决属性名的冲突问题。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)

Symbol特点:

  • Symbol是通过函数Symbol生成的 ,属性名主要有两种类型: 一是原来就有的字符串,另一种就是新增的Symbol类型
let s = Symbol();
typeof s // symbol
  • Symbol的值是唯一的,用来解决命名冲突的问题
let s2 = Symbol('test');
let s3 = Symbol('test');

s2 === s3 // false
  • Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型
let s4 = new Symbol('test'); // TypeError: Symbol is not a constructor
  • Symbol值不能与其他数据进行运算
let s = Symbol();
let result = s + 100; // TypeError: Cannot convert a Symbol value to a number

2、描述:Symbol.prototype.description,只读属性

Symbol 对象可以通过一个可选的描述创建,可用于调试,也就是它的toString() 值,但不能用于访问 symbol 本身而且两个具有相同的description 的symbol 不相等。

let s2 = Symbol('test');
let s3 = Symbol('test');

String(s2); // Symbol(test)
s2.toString(); //  Symbol(test)

s2.description; // test
s2 === s3; // false

console.log(s2); // Symbol(test)

和Symbol()  不同的是,用 Symbol.for()  方法创建的的 symbol 会被放入一个全局 symbol 注册表中。Symbol.for()并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。假如是,则会直接返回上次存储的那个。否则,它会再新建一个。

let s2 = Symbol.for('test'); // 创建一个 symbol 并放入 symbol 注册表中,键为 "test"
let s3 = Symbol.for('test'); // 从 symbol 注册表中读取键为"foo"的 symbol

String(s2); // Symbol(test)
s2.toString(); // Symbol(test), 既是该 symbol 在 symbol 注册表中的键名,又是该 symbol 自身的描述字符串

s2.description; // test
s2 === s3  // true,证明了上面说的

通常来说,除非你有非常好的理由,否则不应该使用全局注册中心,因为这会造成命名冲突。为了防止命名冲突,最好给你要放入 symbol 注册表中的 symbol 带上键前缀。

3、属性名

由于每一个Symbol值都是不相等的,意味这Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性,也能防止一个对象多个模块构成出现改写或者覆盖的情况。


let userInfo = {
  name: 'test',
  age: 24
};
let methods = {
  up: Symbol(),
  down: Symbol()
};

userInfo[methods.up] = function () {
  console.log('this is up');
}
userInfo[methods.down] = function () {
  console.log('this is down');
}

需要注意的是,Symbol值作为属性名时,该属性是公开属性,不是私有属性。在JavaScript可以很方便的模拟私有属性,symbol不会出现在 Object.keys()的结果中,除非你明确地export 一个symbol,或者用 Object.getOwnPropertySymbols() 函数获取,否则其他代码无法访问这个属性。

function getUserInfo() {
  const name = Symbol('test');
  const obj = {};
  obj[name] =  'test';
  return obj;
}

const userInfo = getUserInfo();
console.log(Object.keys(userInfo)); // []

// 除非用 symbol 的引用,否则无法访问该属性
console.log(userInfo[Symbol('test')]); // undefined

// 用 getOwnPropertySymbols() 可以拿到 symbol 的引用
const [symbol] = Object.getOwnPropertySymbols(userInfo);
console.log(userInfo[symbol]); // 'test'

此外,symbol不会出现在JSON.stringify()的结果里,确切地说是JSON.stringify()会忽略symbol属性名和属性值

const symbol = Symbol('test');
const userInfo = {
  [symbol]: 'test', name: symbol
};

console.log(JSON.stringify(userInfo)); // "{}"

4、 其他

Symbol.iterator: 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用

let myIterable = {};
myIterable[Symbol.iterator] = function* (){
  yield 1;
  yield 2;
  yield 3;
}

console.log([...myIterable]); // [ 1, 2, 3 ]
Symbol.asyncIterator: 符号指定了一个对象的默认异步迭代器。如果一个对象设置了这个属性,它就是异步可迭代对象,可用于for await...of循环。
const myIterable = new Object();
myIterable[Symbol.asyncIterator] = async function* (){
  yield 'hello';
  yield 'async';
  yield 'Iterator';
};

(async ()=> {
  for await (const x of myIterable){
    console.log(x);
  }
})();

// hello
// async
// Iterator
Symbol.match:指定了匹配的是正则表达式而不是字符串。String.prototype.match() 方法会调用此函数。
Symbol.replace:一个替换匹配字符串的子串的方法. 被 String.prototype.replace() 使用
Symbol.search:一个返回一个字符串中与正则表达式相匹配的索引的方法。被String.prototype.search() 使用
class caseInsensitiveSearch{
  constructor(value){
    this.value = value.toLowerCase();
  }
  [Symbol.search](string){
    return string.toLowerCase().indexOf(this.value);
  }
}

console.log('foobar'.search(new caseInsensitiveSearch('sar'))); // -1
console.log('foobar'.search(new caseInsensitiveSearch('bar'))); // 3
Symbol.split: 一个在匹配正则表达式的索引处拆分一个字符串的方法.。被 String.prototype.split() 使用
Symbol.hasInstance: 一个确定一个构造器对象识别的对象是否为它的实例的方法。被 instanceof 使用
Symbol.isConcatSpreadable: 一个布尔值,表明一个对象是否应该flattened为它的数组元素。被 Array.prototype.concat() 使用
Symbol.unscopables: 拥有和继承属性名的一个对象的值被排除在与环境绑定的相关对象外
Symbol.species:一个用于创建派生对象的构造器函数
Symbol.toPrimitive: 个将对象转化为基本数据类型的方法
// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果
var obj1 = {};
console.log(+obj1);     // NaN
console.log(`${obj1}`); // "[object Object]"
console.log(obj1 + ""); // "[object Object]"

// 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果
var obj2 = {
  [Symbol.toPrimitive](hint) {
    if (hint == "number") {
      return 10;
    }
    if (hint == "string") {
      return "hello";
    }
    return true;
  }
};
console.log(+obj2);     // 10      -- hint 参数值是 "number"
console.log(`${obj2}`); // "hello" -- hint 参数值是 "string"
console.log(obj2 + ""); // "true"  -- hint 参数值是 "default"
Symbol.toStringTag: 用于对象的默认描述的字符串值。被 Object.prototype.toString() 使用。
Symbol.for(key): 使用给定的key搜索现有的symbol,如果找到则返回该symbol。否则将使用给定的key在全局symbol注册表中创建一个新的symbol。
Symbol.keyFor(sym): 从全局symbol注册表中,为给定的symbol检索一个共享的?symbol key