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()返回。但它并非私有属性。
- 获取指定对象的所有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)]
- Reflect.ownKeys()。返回所有类型的键名,包括常规键名和Symbol键名。
let obj = {
[Symbol('s')]: 1,
enum: 2,
nonEnum: 3
};
console.log(Reflect.ownKeys(obj));// ["enum", "nonEnum", Symbol(s)]
- 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 值,可以在主页面得到。