Symbol
一种新的原始数据类型,表示独一无二的值。它是JavaScript语言的第七种数据类型。
特性
- 表示独一无二的值
let a = Symbol();
let b = Symbol();
console.log(a === b); // false
console.log(a == b); // false
- 新的原始数据类型
var a = Symbol();
typeof a; // "symbol"
- 不能使用new关键字
var a = new Symbol(); // 报错:Uncaught TypeError: Symbol is not a constructor
- 可以接受一个字符串作为参数,表示对Symbol实例的描述
var a = Symbol('a'); // Symbol(a)
a.toString() // "Symbol(a)"
- Symbol值不能与其他类型的值进行运算
var a = Symbol('a');
'hello ' + a; // 报错:Cannot convert a Symbol value to a string
\`hello ${a}\`; // 报错:Cannot convert a Symbol value to a string
- Symbol值可以显式转为字符串
var a = Symbol('a');
String(a); // "Symbol(a)"
a.toString(); // "Symbol(a)"
- Symbol值也可以转为布尔值。
var a = Symbol('a');
Boolean(a); // true
- 不能转为数值
var a = Symbol('a');
Number(a); // 报错:Cannot convert a Symbol value to a number
作为对象属性名
- 作为属性名的几种写法
var a = Symbol();
var obj = {};
obj[a] = 'hello';
var obj = {
[a]: 'hello'
};
var obj = {};
Object.defineProperty(obj, a, {
value: 'hello'
});
- Symbol值作为对象属性名时,不能用点运算符,因为点运算符后面总是字符串
var a = Symbol();
var obj = {};
obj.a = 'hello';
obj[a] // undefined
obj['a'] // "hello"
- 不能被for...in、for...of遍历,也不会被Object.keys()、Object.getOwnPropertyNames()等方法返回,可以通过Object.getOwnPropertySymbols()获取,或者通过Reflect.ownKey(属于ES7的范畴,它代理了大部分的Object功能,对它不再继续深入,有兴趣的同学可以自行查阅资料)
var obj = {
[Symbol('a')]: 'a',
[Symbol('b')]: 'b',
c: 'c'
}
Object.getOwnPropertyNames(obj); // []
for (var i in obj) {
console.log(i); // 无输出
}
Object.getOwnPropertySymbols(obj); // [Symbol(a), Symbol(b)]
Reflect.ownKeys(obj); // ["c", Symbol(a), Symbol(b)]
Symbol方法和内置Symbol值
for()
- 使用for方法可以获取相同Symbol值的不同变量
- 传入的字符串需要相同,可以为空
- 必须全为for定义
var a = Symbol.for();
var b = Symbol.for();
var c = Symbol.for('c');
var d = Symbol.for('c');
var e = Symbol('e');
var f = Symbol.for('e');
a === b; // true
c === d; // true
e === f; // false
keyFor()
- 返回一个已登记的Symbol类型值的key
- 只有通过for方法生成的Symbol才能使用该方法返回key
var a = Symbol.for('a');
Symbol.keyFor(a); // 'a'
var a = Symbol('a'); // 未登记
Symbol.keyFor(a); // undefined
hasInstance
- 此方法至少暂时不可用,最新版的chrome和node中均不能正常工作
- 该方法会被instanceof运算符调用
class Demo{
static [Symbol.hasInstance](foo){
return true
}
}
[] instanceof Demo // 理论上返回true,但是实际chrome中测试返回false
isConcatSpreadable
- 对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。
var a = ['a']; // 数组的Symbol.isConcatSpreadable属性默认值为true
['b'].concat(a, 'c'); // ['b', 'a', 'c']
var a = ['a'];
a[Symbol.isConcatSpreadable] = false;
['b'].concat(a, 'c'); // ['b', ['a'], 'c']
var a = {
0: 'a',
length: 1
};
['c'].concat(a, 'd'); // ['c', {0: 'a', length: 1}, 'd']
a[Symbol.isConcatSpreadable] = true;
['c'].concat(a, 'd'); // ['c', 'a', 'd']
- 这种方法是行不通的,至少我验证了最新的chrome和最新版的node,都无法正常工作
class Demo extends Array{[Symbol.isConcatSpreadable](){return false}}
var a = new Demo('a');
['b'].concat(a); // ['b', 'a']
species
- 蛋疼啊。。。。
- 最新版Chrome和node中验证失败
class Demo extends Array{
constructor(props){
super();
this.props = props
}
[Symbol.species](){
return Array;
}
}
var a = new Demo(1);
var b = a.map(function(item, i, arr){ // 都是一个不同的数组了,咋可能还是Demo的实例呢!
return item * 2;
});
b instanceof Demo; // 他们说这个地方应该返回true,但实际上返回false
b instanceof Array;
match
- 指定字符串调用match方法时的行为
class Match{
constructor(props){
this.props = props || 'hello'
}
[Symbol.match](string){
return this.props.match(string);
}
}
var match = new Match();
'e'.match(match); // ['e']
var match = new Match('111');
'e'.match(match); // null
replace
- 指定字符串调用replace方法时的行为
class Replace{
[Symbol.replace](string){
console.log(string); // 还可以干点别的哦。。
return string.replace(/b/g, 'a');
}
}
var replace = new Replace();
'abaaaab'.replace(replace); // 'aaaaaaa'
search
- 指定字符串调用search方法时的行为
class Search{
[Symbol.search](string){
string = 'aaaaab';
return string.search(/b/g);
}
}
var search = new Search();
'abaaaab'.search(search); // 5
split
- 指定字符串调用split方法时的行为
class Split{
[Symbol.split](string){
return string.split('b', 1);
}
}
var split = new Split();
'abaaaab'.split(split); // ['a']
iterator
- 见Iterator.md
toPrimitive
- 当数据被进行类型转换时,调用该数据的Symbol.toPrimitive方法
var obj = {
[Symbol.toPrimitive](type){
switch(type){
case 'string': // 只能转换成string时
return 'aaa';
break;
case 'number': // 只能转换成number时
return 123;
break;
case 'default': // 既可以转换成string,又可以转换成number时
return 'default';
break;
default:
throw new Error()
}
}
}
2 * obj // 246
'a' + obj // 'adefault'
String(obj) // 'aaa'
toStringTag
- 控制在调用toString方法时返回的字符串
class Demo{
get [Symbol.toStringTag](){
return 'Demo'
}
}
var a = new Demo();
a.toString(); // "[object Demo]"
应用
消除魔术字符串
在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,该由含义清晰的变量代替。
- 魔术字符串的栗子
function demo(str){
switch(str){
case 'abc': // 魔术字符串
console.log('hello Symbol');
break;
}
}
demo('abc'); // 魔术字符串
- 消除魔术字符串的方法,把它变成一个变量。大家都懂的,就没栗子了
- 但是,上面栗子上'abc'字符串真的有实际意义吗?是否可以这样?当然也是需要赋值给一个变量
var a = Symbol('a'); // 传字符串'a'只是为了方便查看下面的打印结果,其实可以不传
var b = Symbol('b');
function demo(v){
switch(v){
case a:
console.log(\`hello Symbol, I am ${String(a)}\`); // ES6的模板功能,下次再带大家认识。
break;
case b:
console.log(\`hello Symbol, I am ${String(b)}\`);
break;
}
}
demo(a);