Symbol
作用
Symbol表示独一无二的值。在ES5中,对象的属性名都是字符串,而在ES6中,对象的属性名除了可以是字符串,还可以是Symbol类型。
Symbol()前不能使用new,用Symbol()生成的变量是一个原始类型的值,不是对象。
let a = Symbol();
Symbol()函数可以接受一个字符串作为参数,主要是为了显示时易于区分,相当于给每个独一无二的值加个描述。如果传入的不是字符串,会转成字符串。
let a = Symbol('str')
let b = Symbol({})
let c = Symbol([1,2,3])
console.log(a) // Symbol(str)
console.log(b) // Symbol([object Object])
console.log(c) // Symbol(1,2,3)
Symbol.prototype.description
对于我们传进去的参数,也就是刚才说的描述,我们可以通过description属性来获取。
let a = Symbol('hello world')
console.log(a.description) // hello world
属性名的遍历
现在我们知道Symbol类型的值能够作为对象的属性,在开发中,如果我们需要给对象添加一个唯一的属性名,或者我们需要这个属性在一些遍历时不暴露键名,那我们便可以用到Symbol。
let key = Symbol('Symbol key');
let obj = {
[key]: 'symbol key value',
[Symbol()]: 'symbol key value2',
a: 'a'
}
console.log(Object.getOwnPropertyNames(obj)); // ['a']
像是for...in,Object.getOwnPropertyNames(),Object.keys()都是无法获取到Symbol类型的键名的。虽然以上方法无法获取,并不代表Symbol类型的属性名是私有属性,我们可以通过Object.getOwnPropertySymbols()或者Reflect.ownKeys()来获取。
let key = Symbol('Symbol key')
let obj = {
[key]: 'symbol key value',
[Symbol()]: 'symbol key value2',
a: 'a'
}
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(Symbol key), Symbol()]
console.log(Reflect.ownKeys(obj)); // ['a', Symbol(Symbol key), Symbol()]
Symbol.for(), Symbol.keyFor()
使用Symbol.for()同样能得到一个Symbol类型的值,它接受一个字符串参数,如果这个字符串参数相同,则是同一个值。
let a = Symbol.for('hello');
let b = Symbol.for('hello');
a === b // true
所以如果我们用Symbol.for()的同一个Symbol值来做对象的键名,那仍符合后面覆盖前面的原则。
let key = Symbol.for('hello');
console.log(typeof key);
let obj = {
[key]: '1',
[Symbol.for('hello')]: '2'
}
console.log(obj); // {Symbol(hello): '2'}
Symbol.keyFor()返回一个Symbol类型值的登记记录,即有没有用过Symbol.for()
let s1 = Symbol.for('hello');
let s2 = Symbol();
console.log(Symbol.keyFor(s1)); // hello
console.log(Symbol.keyFor(s2)); // undefined
内置的Symbol值
Symbol.hasInstance
对象的Symbol.hasInstance属性,指向一个内部方法。当对象调用instanceof运算时,涉及到这个方法。比如我们可以自定义instanceof的判断:
class Test {
// 我自定义为如果一个对象有a属性,则是该类对象的实例
[Symbol.hasInstance](val) {
return Reflect.has(val, 'a');
}
}
let obj1 = {a: '123'}
let obj2 = {b: '456'}
console.log(obj1 instanceof new Test()) // true
console.log(obj2 instanceof new Test()) // false
Symbol.iterator
对象的Symbol.iterator属性,指向该对象的默认遍历器方法。运用该方法,我们可以实现自己的迭代器对象。在前面章节如变量的解构赋值、数组的扩展等章节中,已经详细实现过,不再赘述。
let obj = {
value: [1,2,3],
[Symbol.iterator]() {
let _this = this;
let index = 0;
return {
next() {
if (index < _this.value.length) {
return {
value: _this.value[index++],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
}
for(let item of obj) {
console.log(item); // 1 2 3
}
其他一些属性
Symbol.isConcatSpreadable,对象属性,可以使布尔值或undefined。用来设置数组对象或类数组对象在数组concat时的展开行为。Symbol.species,指向一个构造函数。Symbol.match,指向一个方法。Symbol.replace,指向一个方法Symbol.search,指向一个方法Symbol.split,指向一个方法Symbol.toPrimitive,指向一个方法,涉及到类型转换。Symbol.toStringTagSymbol.unscopables
如何实现一个Symbol
(function () {
var root = this;
var generateName = (function () {
// 保证在转成字符串时唯一
var postfix = 0;
return function (desc) {
postfix++;
return `$$${desc}_${postfix}`;
};
})();
var MySymbol = function (description) {
// 不能使用new命令
if (this instanceof MySymbol) {
throw new TypeError("Symbol is not a constructor");
}
// 接收一个参数
var desc =
description === undefined
? undefined
: String(description);
var symbol = Object.create({
toString: function () {
return this.__Name__;
},
});
Object.defineProperties(symbol, {
__Description__: {
value: desc,
writeable: false,
enumerable: false,
configurable: false,
},
__Name__: {
value: generateName(desc), //接收的参数用来当描述符
writeable: false,
enumerable: false,
configurable: false,
},
});
return symbol;
};
var forMap = {};
Object.defineProperties(MySymbol, {
for: {
// Symbol.for()
value: function (description) {
var desc =
description === undefined
? undefined
: String(description);
if (!Reflect.has(forMap, desc)) {
Reflect.set(forMap, desc, MySymbol(desc))
}
return Reflect.get(forMap, desc);
},
writable: true,
enumerable: false,
configurable: true,
},
keyFor: {
// Symbol.keyFor()
value: function (symbol) {
for (var key in forMap) {
if (Reflect.get(forMap, key) === symbol)
return key;
}
},
writable: true,
enumerable: false,
configurable: true,
},
});
root.MySymbol = MySymbol;
})();
let symbol1 = MySymbol('1')
let symbol2 = MySymbol('1');
let symbol3 = MySymbol.for('hello');
let symbol4 = MySymbol.for('hello');
console.log(symbol1 === symbol2); // false
console.log(symbol2 === symbol3); // false
console.log(symbol3 === symbol4); // true
console.log(MySymbol.keyFor(symbol3)); // hello
let symbol5 = new MySymbol(); // Uncaught TypeError: Symbol is not a constructor