Symbol是JavaScript的第七种数据类型,是原始数据类型,表示独一无二的值。它是ES6新引入的数据类型,引入的原因在于:ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。

创建和使用Symbol()

1、创建一个symbol类型的数据时,Symbol前不可以使用new操作符,因为Symbol值不是对象,是一个值。

let s = Symbol();

2、Symbol的参数

  • 如果Symbol的参数是一个对象,就会调用该对象的toString()方法,将其转为字符串,然后才生成一个Symbol值
const obj = {
  toString() {
    return 'sss';
  }
};
const s = Symbol(obj);
console.log(s); // Symbol(sss)
  • Symbol函数的参数只是对Symbol值的描述,因此即使参数相同的两个Symbol函数的返回值是不同的
let s1 = Symbol();
let s2 = Symbol();
console.log(s1 === s2); // false

let s1 = Symbol('foo');
let s2 = Symbol('foo');
console.log(s1 === s2); // false

3、Symbol值不能与其他类型的值运算

let s = Symbol('s is a data');
console.log(s + "Symbol");   // TypeError: can't convert symbol to string

4、Symbol值转化为其他类型

  • 转为字符串
let s = Symbol('My symbol');
console.log(String(s)); // 'Symbol(My symbol)'
console.log(toString(s)); // 'Symbol(My symbol)'
  • 转为布尔值,但不可以转为数字
let s = Symbol();
console.log(Boolean(s)); // true
console.log(!s);  // false
console.log(Number(s));  // TypeError

5、ES2019提供了一个实例属性description,直接返回Symbol的描述

const s = Symbol('symbol');
console.log(s.description);  //symbol

6、由于每一个symbol值都不相同,Symbol值可以作为标识符,用于对象的属性名。

let mySymbol1 = Symbol();
let mySymbol2 = Symbol();
let mySymbol3 = Symbol();

//三种写法
let obj = {
    [mySymbol1]:"red"
};

obj[mySymbol2] = 'green';

Object.defineProperty(obj, mySymbol3, { value:"blue" });
const mySymbol = Symbol();
const obj = {};

obj.mySymbol = 'point';
console.log(obj[mySymbol]); // undefined
console.log(obj['mySymbol']); // "point"
  • 在对象内部定义属性要使用[],如果Symbol值不放在[]中,该属性名就是字符串,而不是Symbol值了
let s = Symbol();
let obj = {
  [s]: function (arg) { ... }
};
obj[s](123);

几个相关的方法

遍历对象时Symbol作为属性名不会出现在for..in、for..of循环,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但它并非私有属性。

  1. 获取指定对象的所有Symbol属性名:Object.getOwnPropertySymbols()。当前对象的所有用作属性名Symbol值以数组的形式被该方法返回。
const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'red';
obj[b] = 'blue';

console.log(Object.getOwnPropertySymbols(obj));  //[Symbol(a), Symbol(b)]

对比for..in循环与Object.getOwnPropertyNames():这两种方法都无法得到Symbol属性名

const obj = {};
const s = Symbol('sss');
obj[s] = 'yyy';

for (let i in obj) {
  console.log(i); // 无输出
}

console.log(Object.getOwnPropertyNames(obj)); // []

consoel.log(Object.getOwnPropertySymbols(obj)); // [Symbol(sss)]
  1. Reflect.ownKeys()。返回所有类型的键名,包括常规键名和Symbol键名。
let obj = {
  [Symbol('s')]: 1,
  enum: 2,
  nonEnum: 3
};

console.log(Reflect.ownKeys(obj));//  ["enum", "nonEnum", Symbol(s)]
  1. Symbol.for() 。接收一个字符串作为参数,搜索是否有以该参数作为名称的Symbol值。有就返回这个Symbol值,否则就新建一个以该字符串为名称的Symbol值,并将其注册到全局。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

console.log(s1 === s2); // true

 上面这个栗子中,s1是通过Symbol.for()创建的,因此可知,Symbol.for()和Symbol()这两种写法,都会生成新的 Symbol。区别在于:

Symbol.for(),会被登记在全局环境中以供搜索 ,不会每次调用都返回一个新的Symbol类型的值。

Symbol(),不会登记在全局环境中,每次调用都会创建一个新的Symbol类型的值。 

consol.log(Symbol.for("bar") === Symbol.for("bar"));  // true
console.log(Symbol("bar") === Symbol("bar"));  // false


let s1 = Symbol.for("s");
console.log(Symbol.keyFor(s1)); // s
let s2 = Symbol("s");
console.log(Symbol.keyFor(s2)); // undefined

 Symbol.for()是为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。

function fn() {
  return Symbol.for('s');
}

const x = fn();
const y = Symbol.for('s');
console.log(x === y); // true

Symbol.for()的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同一个值。 

iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);

iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
// true

 上面代码中,iframe 窗口生成的 Symbol 值,可以在主页面得到。