本文是ES2015系列的第四篇文章,主要是跟大家一起探讨关于ES2015的新类型Symbol。尽管对于该类型接触这么久,但是却很少在日常使用它,所以今天正好抽时间去查阅了一些资料,总结了一些内容,欢迎大家一起探讨如何使用该类型,以及对于Symbol的引入是否会改善原有的js代码的一些问题。
类型声明与方法
1. Symbol()
Symbol被引入JavaScript中,成为了同Number,Boolean,String一样的基本类型。这也是自从1997年JavaScript被标准化后第一次增加新的基本类型(ES5版本有六种基本类型)。但是他的初始方法又跟原来的基本类型有所不同,比如我们无法直接创建两个完全相同的Symbol对象:
var a=Symbol("helloworld")
typeof a // "symbol"
var b=Symbol("helloworld")
a===b
// false
a==b
// false
代码中我们创建了两个symbol类型,通过使用 typeof 就能看出他的类型名称,并且Symbol()中的参数也是可选的,参数主要是用于对于symbol的描述,可以传递一个字符串内容。注意,这里我们使用同样的参数初始化的两个symbol对象是完全不同的两个对象。这也是Symbol的设计初衷,创建永远不会冲突的对象。我们可以使用该值来定义属性名称等,这样这个名称就永远不会与其他的名称发生冲突。
2. Symbol.for()
Symbol.for()函数同样可用于生成一个Symbol对象,不同在于如果传递的参数产生的Symbol已经被注册过则返回注册的对象,否则产生一个新的对象并注册到全局。通过下面的例子对比一下使用方式
我们定义了一个单例模式,该单例模式的实例属性为一个Symbol类型。
//定义一个微笑计数器
const INSTANCE = Symbol( "instance" );
function Smile(){
if(Smile[INSTANCE]){
return Smile[INSTANCE]
}else{
var countSmile=0
var smile=()=>{
console.log("Big smile:"+countSmile++)
}
return Smile[INSTANCE]={
smile
}
}
}
var a=Smile()
var b=Smile()
a.smile()
b.smile()
a.smile()
console.log(a===b)
// Big smile:0
// Big smile:1
// Big smile:2
// true
如果我们将const INSTANCE = Symbol( "instance" );
代码移动到Smile的里面去是否还是单例模式?答案是no,由于每次执行Smile的时候生成的Symbol各不相同,无法通过其查找到特定对象 ,就会重新生成一个新的实例对象。 不过通过Symbol.for可以帮助我们查询是否在全局作用域生成过一个以该字符串作为参数的Symbol。如果存在则返回,如果不存在则创建,有点单例的感觉。所以修改后的代码变为了:
function Smile(){
const INSTANCE=Symbol.for("Hello world")
if(Smile[INSTANCE]){
return Smile[INSTANCE]
}else{
var countSmile=0
var smile=()=>{
console.log("Big smile:"+countSmile++)
}
return Smile[INSTANCE]={
smile
}
}
}
var a=Smile()
var b=Smile()
a.smile()
b.smile()
a.smile()
console.log(a===b)
可是这样又会引入新的问题,其他函数是否也可以获得该值呢,答案是yes。所以我们在定义字符串的时候可以尽量依赖于函数或者类 比如上面的如果我们改为:Symbol.for(“Smile.Instance”),这种定义估计不会再其他的地方出现吧。
3. Symbol.keyFor()
另外一个方法是keyFor(),该方法允许我们获得对应Symbol类型的初始化键名称,前提是该值通过Symbol.for方法注册到了全局。
var a=Symbol.for("Mike")
var b=Symbol("Alice")
console.log(Symbol.keyFor(a)) //Mike
console.log(Symbol.keyFor(b)) //undefined 由于未注册原因,无法查找到
4. Object.getOwnPropertySymbols
该函数并非Symbol的方法,但是可以通过该方法获得一个对象中使用Symbol创建的属性名称,另外对象中使用Symbol来创建属性,该属性是不能被枚举的,使用常规的for…of,Object.getOwnPropertyNames是无法获得的,这从某种程度上做到了信息的隐藏。
var Person={
name:'Mike',
country:"china",
[Symbol("info")]:{
foo:"foo",
bar:"bar"
}
}
Object.getOwnPropertySymbols(Person)
// [Symbol(info)]
Object.getOwnPropertyNames(Person)
// ["name", "country"]
for(var i in Person){
console.log(i)
}
// name
// country
简单的使用案例
我们可以在定义一些常量字符串或者数值的时候,考虑使用symbol类型,使用symbol可以防止定义或者将来扩展增加新的属性的时候与原有的属性发生冲突。如果使用字符串直接定义当然也可以,只是不再是独一无二的名称。
var log={
// 定义日志的基本类型
levels:{
DEBUG: Symbol.for('log.debug'),
INFO: Symbol.for('log.info'),
WARN: Symbol.for('log.warn'),
},
//三种处理日志的方式
warn(message){
...
},
info(message){
...
},
debug(message){
...
},
// 记录日志方法
record(level,message){
switch(level){
case Symbol.for('debug'):
this.warn(message)
break
case Symbol.for('info'):
this.info(message)
break
case Symbol.for('warn'):
this.warn(message)
break
default:
this.debug(message)
}
}
}
log.record(log.levels.DEBUG, 'debug message');
log.record(log.levels.INFO, 'info message');
Andreas Rossberg是Google的V8项目的技术负责人,曾在stackoverflow上回答过为什么引入该类型,其实最初提议的目的是想使用他来完成对象的私有属性定义,但是通过几次变更后,该提议却貌似有点跑题,毕竟最终我们看到的这个Symbol 不是私有的,我们还是可以通过各种方式,比如Object.getOwnPropertySymbols来获得该属性,现在Symbol依赖其独一无二的特性,主要是防止冲突的属性存在。不知大家是否有更多好的使用场景与案例,可以一起分享讨论。
是否引入symbol这个问题应该由es6的委员会相关人员来回答才最合适啊,Andreas Rossberg只是实现者,可能并不完全知道symbol的设计目的,难道Andreas Rossberg也是委员会的成员?
var s = Symbol() var obj = {s:1,[s]:2} obj.s == obj[s]//false Symbol有时候会有造成一些令人迷惑的地方,例如上面。按照以往的经验obj.s 和obj[s]是相等的,都是访问属性的一种方式,但是现在不一样了,用中括号去访问属性可能并不是你想要的结果。