Symbol
ES5的对象属性名都是字符串,这容易造成属性名的冲突。如果存在一种机制,能够保证每个属性的名字都是独一无二的就可以解决这个问题。因此ES6中引入了Symbol。
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。可以称之为JavaScript的第七种数据类型。
Symbol是通过symbol函数生成的。也就是说,对象的属性名可以有两种类型,一种是原来就有的字符串,另一种是新增的Symbol类型。凡是属性名属于Symbol的,就是独一无二的,可以保证不会与其他属性名发生冲突。
| let s = Symbol(); typeof s
|
Symbol
函数可以接收一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示或者是转为字符串的过程中方便区分。
1 2 3 4 5 6 7 8
| var s1 = Symbol('foo'); var s2 = Symbol('bar');
s1 s2
s1.toString() s2.toString()
|
如果Symbol的参数是一个对象,那么就会调用该对象的toString()
方法,将其转换为字符串,然后才生成一个Symbol值。
1 2 3 4 5 6 7 8
| const obj = { toString() { return 'abc'; } };
const sym = Symbol(obj); sym
|
Symbol值不能与其他类型的值进行运算,会报错;但是却可以显式转换为字符串
1 2 3 4 5 6
| var sym = Symbol('My Symbol');
"your symbol is " + sym
String(sym); sym.toString();
|
作为属性名的Symbol
由于每一个Symbol值都是不相等的,这意味着Symbol可以作为标识符,用在对象的属性名,就能保证不会出现同名的属性。可以防止某一个键被不小心改写或覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13
| var mySymbol = Symbol();
var a = {}; a[mySymbol] = 'Hello!';
var a = { [mySymbol]: 'Hello' };
var a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello' });
a[mySymbol]
|
Symbol值作为对象属性名时,不能用作点运算符。
1 2 3 4 5 6
| var mySymbol = Symbol(); var a = {};
a.mySymbol = 'Hello'; a[mySymbol] a['mySymbol']
|
Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的。
1 2 3 4 5 6 7 8
| log.levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn') };
log(log.levels.DEBUG, 'debug message'); log(log.levels.INFO, 'info message');
|
还有一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var shapeType = { triangle: Symbol() };
function getArea(shape, options) { var area = 0;
switch(shape) { case shapeType.triangle: area = .5 * options.width * options.height; break; }
return area; }
getArea(shapeType.triangle, {width: 100, height: 100});
|
属性名的遍历
Symbol作为属性名,不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols
方法,可以获取指定对象的所有Symbol属性名。
1 2 3 4 5 6 7 8 9 10
| var obj = {}; var a = Symbol('a'); var b = Symbol('b');
obj[a] = 'Hello'; obj[b] = 'World';
var objectSymbols = Object.getOwnPropertySymbols(obj);
objSymbols
|
由于以Symbol值作为名称的属性,不会被常规方法遍历得到。因此可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| var size = Symbol('size');
class Collection { constructor() { this[size] = 0; } add(item) { this[this[size]] = item; this[size]++; } static sizeOf(instance) { return instance[size]; } }
var x = new Collection(); Collection.sizeOf(x)
x.add('foo'); Collection.sizeOf(x)
Object.keys(x) Object.getOwnPropertyNames(x) Object.getOwnPropertySymbols(x)
|
Symbol.for(), Symbol.keyFor()
有时候希望重新使用同一个Symbol值,Symbol.for
可以做到这一点。其接受一个字符串为参数,然后搜索有没有已该参数作为名称的Symbol值,有就返回,没有就以这个名称创建一个Symbol值。
1 2 3
| var s1 = Symbol.for('foo') var s2 = Symbol.for('foo') s1 === s2
|
Symbol.keyFor
方法返回一个已经登记的Symbol类型值的key
1 2 3 4 5
| var s1 = Symbol.for("foo"); Symbol.keyFor(s1)
var s2 = Symbol("Foo"); Symbol.keyFor(s2)
|
模块的单例模式
1 2 3 4 5 6 7 8 9 10 11
| const FOO_KEY = Symbol.for('foo');
function A() { this.foo = 'hello'; }
if (!global[FOO_KEY]) { global[FOO_KEY] = new A(); }
module.exports = global[FOO_KEY];
|
上面的代码中,可以保证global[FOO_KEY]
不会被无意间覆盖,但还是可以被改写。
内置的Symbol值
除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法。
Symbol.hasInstance
指向一个内部方法。当其他对象使用instanceof
运算符,判断是否为该对象的实例时,会调用此方法。例如:
1 2 3 4 5 6 7
| class MyClass { [Symbol.hasIntance](foo) { return foo instanceof Array; } }
[1, 2, 3] instanceof new MyClass()
|
Symbol.isConcatSpreadable
等于一个布尔值,表示该对象使用Array.prototype.concat()
时,是否可以展开。默认情况下是可以展开的。
1 2 3 4 5 6 7
| let arr1 = ['c', 'd']; ['a', 'b'].concat(arr1, 'e'] arr1[Symbol.isConcatSpreadable]
let arr2 = ['c', 'd']; arr2[Symbol.isConcatSpreadable] = false; ['a', 'b'].concat(arr2, 'e']
|
Symbol.species
指向一个方法,该对象作为构造函数创造实例时,会调用这个方法,如果this.constructor[Symbol.species]
存在,会使用这个属性作为构造函数,来创造新的实例对象。
1 2 3 4
| static get [Symbol.species] { return this; }
|
Symbol.match
指向一个函数,当执行str.match(myObject)
时,如果该属性存在,会调用它,返回该方法的返回值。
1 2 3 4 5 6 7 8 9 10 11
| String.prototype.match(regexp);
regexp[Symbol.match](this);
class MyMatcher { [Symbol.match](string) { return 'hello world'.indexOf(string); } }
'e'.match(new MyMatcher()];
|
Symbol.replace
指向一个方法,当该对象被Symbol.prototype.replace
方法调用时,会返回该方法的返回值。
1 2 3
| String.prototype.replace(searchValue, replaceValue);
searchValue[Symbol.replace](this, replaceValue);
|
下面有一个例子说明这个问题:
1 2 3 4
| const x = {}; x[Symbol.replace] = {...s} => console.log(s);
'Hello'.replace(x, 'World')
|
第一个参数是replace方法正在坐拥的对象,第二个参数替换后的值。
Symbol.search
指向一个方法,当该对象被String.prototype.search
方法调用时,会返回该方法的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13
| String.prototype.search(regexp);
regexp[Symbol.search](this);
class MySearch { constructor(value) { this.value = value; } [Symbol.search](string) { return string.indexOf(this.value); } } 'foobar'.search(new MySearch('foo'))
|
Symbol.split
指向一个方法,当该对象被String.prototype.split
方法调用时,会返回该方法的返回值。
1 2 3
| String.prototype.split(separator, limit);
separator[Symbol.split](this, limit);
|
Symbol.iterator
指向该对象的默认遍历方法
1 2 3 4 5 6 7 8
| var myIterable = {}; myIterable[Symbol.iterator] = function* () { yield 1; yield 2; yield 3; };
[...myIterable]
|
Symbol.toPrimitive
指向一个方法。该对象被转化为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
该方法被调用时,会接受一个字符串参数,表示当前运算的模式,共有三种模式:
- Number:该场合需要转换成数值
- String:该场合需要转换成字符串
- Defaul:该场合既可以转换成数值,也可以转换成字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let obj = { [Symbol.toPrimitive](hint) { switch(hint) { case 'number': return 123; case 'string': return 'str'; case 'default': return 'default'; default: throw new Error(); } } };
2 * obj 3 + obj obj == 'default' String(obj)
|
Symbol.toStringTag
指向一个方法,在该对象上面调用Object.prototye.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型。这个属性可以用于定制[object Object]
或者[object Array]
中object
后面的那个字符串。
1 2 3 4 5 6 7 8 9 10 11
| ({[Symbol.toStringTag]: 'Foo'}.toString())
class Collection { get [Symbol.toStringTag]() { return 'xxx'; } }
var x = new Collection(); Object.prototype.toString.call(x);
|
Symbol.unscopables
指向一个对象。该对象指定了使用with
关键字时,哪些属性会被with
环境排除。
1 2 3
| Array.prototype[Symbol.unscopables]
Object.keys(Array.prototype[Symbol.unscopables])
|
上面的代码说明,数组有6个属性,会被with
命令排除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class MyClass { foo() {return 1;} }
var foo = function () {return 2;}
with [MyClass.prototype] { foo(); }
class MyClass { foo() {return 1;} get [Symbol.unscopables]() { return {foo: true}; } }
var foo = function() {return 2;} with (MyClass.prototype) { foo(); }
|