ES6 为 JavaScript 引入了一种新的基本类型:Symbol,它由全局 Symbol() 函数建立,每次调用 Symbol()函数,都会返回一个惟一的 Symbol。javascript
let symbol1 = Symbol(); let symbol2 = Symbol(); console.log( symbol1 === symbol2 ); > false
由于每一个 Symbol 值都是惟一的,所以该值不与其它任何值相等。html
Symbol 是 JavaScript 中的新原始类型。java
console.log( typeof symbol1 ); > "symbol"
Symbol 充当惟一的对象键。node
let myObject = { publicProperty: 'Value of myObject[ "publicProperty" ]' }; myObject[ symbol1 ] = 'Value of myObject[ symbol1 ]'; myObject[ symbol2 ] = 'value of myObject[ symbol2 ]'; console.log( myObject ); > Object > publicProperty: "Value of myObject[ "publicProperty" ]" > Symbol(): "Value of myObject[ symbol1 ]" > Symbol(): "value of myObject[ symbol2 ]" > __proto__: Object console.log( myObject[ symbol1 ] ); > Value of myObject[ symbol1 ]
当控制台打印myObject
时,你能看到两个 Symbol 值都存储在对象中。"Symbol()"
是调用toString()
的返回值,此值表示控制台中存在 Symbol 键。若是咱们想访问正确的 Symbol,能够检索相应的值。es6
Symbol 键的属性不会在对象的 JSON 中显示,也不会在 for-in 循环和Object.keys
中被枚举出来:markdown
JSON.stringify( myObject ) > "{"publicProperty":"Value of myObject[ \"publicProperty\" ] "}" for( var prop in myObject ) { console.log( prop, myObject[prop] ); } > publicProperty Value of myObject[ "publicProperty" ] console.log( Object.keys( myObject ) ); > ["publicProperty"]
即便 Symbol 键的属性没有在上述案例中出现,这些属性在严格意义上也不是彻底私有的。Object.getOwnPropertySymbols
提供了一种检索对象的 Symbol 键的方法。闭包
Object.getOwnPropertySymbols(myObject) > [Symbol(), Symbol()] myObject[ Object.getOwnPropertySymbols(myObject)[0] ] > "Value of myObject[ symbol1 ]"
若是你使用 Symbol 键来表示私有变量,要确保不要用Object.getOwnPropertySymbols
来检索可能私有化的属性。在这种状况下,Object.getOwnPropertySymbols
的惟一使用状况就是测试和调试。函数
只要你遵循上述规则,从代码开发的角度来看,对象键值是私有的,但在实际状况中,其余人仍能访问你的私有值。测试
虽然 Symbol 键不能被for...of
,扩展运算符和Object.keys
枚举,但它们仍被包含在浅拷贝里:ui
clonedObject = Object.assign( {}, myObject ); console.log( clonedObject ); > Object > publicProperty: "Value of myObject[ "publicProperty" ]" > Symbol(): "Value of myObject[ symbol1 ]" > Symbol(): "value of myObject[ symbol2 ]" > __proto__: Object
正确命名 Symbol 对指明其用途相当重要,若是你须要额外的语义指导,还可在 Symbol 上附上一个描述。Symbol 的描述体如今 Symbol 的字符串值中。
let leftNode = Symbol( 'Binary tree node' ); let rightNode = Symbol( 'Binary tree node' ); console.log( leftNode ) > Symbol(Binary tree node)
始终提供 Symbol 的描述,并始终保持描述的惟一性。若是用 Symbol 访问私有属性,请将其描述视为变量名。
若是你将相同的描述传递给两个 Symbol,它们的值仍不相同。
console.log( leftNode === rightNode ); > false
ES6 有一个用于建立 Symbol 的全局资源:Symbol 注册表,它为字符串和 Symbol 提供了一对一的关系。注册表使用 Symbol.for( key )
返回 Symbol。
当出现key1 === key2
时就会有Symbol.for( key1 ) === Symbol.for( key2 )
。这种对应关系甚至是跨 service worker 和 iframe 的。
let privateProperty1 = Symbol.for( 'firstName' ); let privateProperty2 = Symbol.for( 'firstName' ); myObject[ privateProperty1 ] = 'Dave'; myObject[ privateProperty2 ] = 'Zsolt'; console.log( myObject[ privateProperty1 ] ); // Zsolt
由于 Symbol 注册表中的 Symbol 值和字符串之间有一一对应的关系,因此咱们也能够检索字符串键。使用Symbol.keyFor
方法。
Symbol.keyFor( privateProperty1 ); > "firstName" Symbol.keyFor( Symbol() ); > undefined
即便 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 就好了。
外部库的作法也是这样。
这里有一些比较经常使用的 Symbol,用以访问和修改内部 JavaScript 行为。你能够用它们从新定义内置方法。运算符和循环。
演练1.用下划线来表示字段的私有,有什么利弊?用这种方法和 Symbol 比较。
let mySquare { _width: 5, getWidth() { return _width; } }
利:
弊:
Object.keys
和for..of
循环枚举。演练2. 模拟 JavaScript 中的私有字段。
解决方案:当涉及到构造函数时,能够使用var
, let
, 或 const
在构造函数中声明私有成员。
function F() { let privateProperty = 'b'; this.publicProperty = 'a'; } let f = new F(); // f.publicProperty returns 'a' // f.privateProperty returns undefined
为了对类使用相同的方法,咱们必须放置方法定义:在可访问私有属性的做用域中的构造函数方法中使用私有属性的方法。咱们将使用Object.assign
来达到此目的。(灵感来自Managing private data of ES6 classes)
class C { constructor() { let privateProperty = 'a'; Object.assign( this, { logPrivateProperty() { console.log( privateProperty ); } } ); } } let c = new C(); c.logPrivateProperty();
字段privateProperty
在对象c
中不可访问。
该解决方案也适用于咱们扩展 C 类。