考虑到以下场景:
// a library
var L = (function() {
var prop1 = 'prop1';
function haha() { console.log('world haha'); }
// ...
return {
prop1 : prop1,
haha : someFn
};
})();
// using
L.haha = function() { console.log('hello world!'); };
// call
L.haha(); // hello world!
看出问题来了么?一个公共库暴露出了一些方法和属性,但是在我们调用的时候我们并不知道它其中有什么方法或者属性,这样当我们在这个库上进行定义了相同的属性的时候,这个库就会被暴露的属性就有可能会被重写。
所以为了保证变量的唯一性,ES6在原本的6中基础数据类型(Undefined、Null、Boolean、String、Number、Object)下中引入了Symbol类型,它是独一无二的。
// using
var haha = Symbol();
L[haha] = function() { console.log('hello world!'); };
// call
L.haha(); // world haha
L[haha](); // hello world!
Symbol值通过Symbol
函数生成,由于生成的值是一个原始类型,不是对象,所以不能使用new
关键字,否则会报错。Symbol值是唯一的,不会和其他属性名产生冲突。
var symbol1 = Symbol();
var symbol2 = Symbol();
console.log(typeof symbol1); // symbol
symbol1 === symbol2; // false
// Uncaught TypeError: Symbol is not a constructor
var symbol = new Symbol();
注:Symbol
值不能和其他值运算,否则会报错。
var s1 = 'str1';
var symbol = Symbol();
// Uncaught TypeError: Cannot convert a Symbol value to a string
console.log(s1 + symbol);
Symbol函数可以接受一个字符串作为参数,表示对这个Symbol值的描述。如果传递的是一个对象,那么会调用这个对象的toString
方法。
var foo = Symbol('foo');
var bar = Symbol('bar');
var obj = {
toString: function() {
return 'hello';
}
};
var o = Symbol(obj);
console.log(foo); // Symbol(foo)
console.log(bar); // Symbol(bar)
console.log(o); // Symbol(hello)
前面介绍到了,为了保证属性名的不冲突才引入了Symbol
。作为对象的属性名,Symbol主要有一下三种写法:
let obj = {};
let s = Symbol();
// 第一种写法
obj[s] = 'mySymbol';
// 第二种写法
obj = {
[s]: 'mySymbol'
}
// 第三种写法
Object.defineProperty(obj, s, { value: 'mySymbol' });
obj.s; // undefined
obj.s = 'mySymbol2';
obj[s] // mySymbol2
obj['s'] // mySymbol
Symbol值作为属性,无法使用for...in
或者for...of
来获取,也无法使用Object.keys
或者Object.getOwnPropertyNames
来获取。那么通过什么办法来获取Symbol属性值呢?我们可以使用Object.getOwnPropertySymbols()
来获取一个对象上的属性名,通过Reflect.ownKeys()
来获取所有类型的属性名。
let s1 = Symbol('s1');
let s2 = Symbol('s2');
let testObj = {
normal: 'normal',
[s1]: s1,
[s2]: s2
};
// for...in
for (let key in testObj) {
if (Object.prototype.hasOwnProperty.call(testObj, key)) {
console.log('key', key); // key normal
}
}
// getOwnPropertyNames
console.log(Object.getOwnPropertyNames(testObj)); // ["normal"]
// getOwnPropertySymbols
console.log(Object.getOwnPropertySymbols(testObj)); // [Symbol(s1), Symbol(s2)]
// Reflect.ownKeys
console.log(Reflect.ownKeys(testObj)); // ["normal", Symbol(s1), Symbol(s2)]
如果想获取同一个Symbol值,我们可以给Symbol.for()
中传递一个字符串参数。生成的这个Symbol值会在全局环境中登记,如果下次还传输这个相同的key
值的时候,就会在全局环境中搜索是否有已经存在了这个key
值的Symbol值,如果有就会返回。Symbol.keyFor
方法返回一个已登记的 Symbol 类型值的key
。
let s1 = Symbol.for('s1');
let s2 = Symbol.for('s1');
s1 === s2; // true
console.log(Symbol.keyFor(s1)); // s1
作为对象的内置Symbol属性值,会在特定的情景下触发。比如Symbol.split
会在该对象被String.prototype.split
调用时触发,而Symbol.itertor
指向的函数会在使用for...of
遍历这个对象的时候触发。具体其他的内置属性可以参看文档http://es6.ruanyifeng.com/#docs/symbol#内置的Symbol值;
即使 Symbol 不能使属性私有,它们也能用作带有私有属性的符号。你可以使用 Symbol 来分隔公有和私有属性的枚举,Symbol 能使它更清楚。
const _width = Symbol('width');
class Square {
constructor( width0 ) {
this[_width] = width0;
}
getWidth() {
return this[_width];
}
}
只要你能隐藏_width
就行了,隐藏_width
的方法之一是创建闭包:
let Square = (function() {
const _width = Symbol('width');
class Square {
constructor( width0 ) {
this[_width] = width0;
}
getWidth() {
return this[_width];
}
}
return Square;
} )();
这样做的好处是,他人很难访问到我们对象的私有_width
值,而且也能很好地区分,哪些属性是公有的,哪些属性是私有的。但这种方法的缺点也很明显:
Object.getOwnPropertySymbols
,我们可以使用 Symbol 键。如果你要用 Symbol 来表示私有字段,那你需要表明哪些属性不能被公开访问,如若有人试图违背这一规则,理应承担相应的后果。
枚举允许你定义具有语义名称和唯一值的常量。假定 Symbol 的值不同,它们能为枚举类型提供最好的值。
const directions = {
UP : Symbol( 'UP' ),
DOWN : Symbol( 'DOWN' ),
LEFT : Symbol( 'LEFT' ),
RIGHT: Symbol( 'RIGHT' )
};
当使用 Symbol 作为变量时,我们不必建立可用标识符的全局注册表,也不必费心思想标识符名字,只需要创建一个 Symbol 就行了。