你知道JavaScript Symbol类型怎么用吗
Symbol类型
根据规范,对象的属性键只能是字符串类型或者Symbol类型。不是Number,也不是Boolean,只有字符串或Symbol这两种类型。
到目前为止,我们只见过字符串。现在我们来看看Symbol能给我们带来什么好处。
Symbol
"Symbol"值表示唯一的标识符。
可以使用Symbol()来创建这种类型的值:
//id是symbol的一个实例化对象 letid=Symbol();
创建时,我们可以给Symbol一个描述(也称为Symbol名),这在代码调试时非常有用:
//id是描述为"id"的Symbol letid=Symbol("id");
Symbol保证是唯一的。即使我们创建了许多具有相同描述的Symbol,它们的值也是不同。描述只是一个标签,不影响任何东西。
例如,这里有两个描述相同的Symbol——它们不相等:
letid1=Symbol("id"); letid2=Symbol("id"); alert(id1==id2);//false
如果你熟悉Ruby或者其他有"Symbol"的语言——别被误导。JavaScript的Symbol是不同的。
注意:Symbol不会被自动转换为字符串
JavaScript中的大多数值都支持字符串的隐式转换。例如,我们可以alert任何值,都可以生效。Symbol比较特殊,它不会被自动转换。
例如,这个alert将会提示出错:
letid=Symbol("id"); alert(id);//类型错误:无法将Symbol值转换为字符串。
这是一种防止混乱的“语言保护”,因为字符串和Symbol有本质上的不同,不应该意外地将它们转换成另一个。
如果我们真的想显示一个Symbol,我们需要在它上面调用.toString(),如下所示:
letid=Symbol("id"); alert(id.toString());//Symbol(id),现在它有效了
或者获取symbol.description属性,只显示描述(description):
letid=Symbol("id"); alert(id.description);//id
“隐藏”属性
Symbol允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。
例如,如果我们使用的是属于第三方代码的user对象,我们想要给它们添加一些标识符。
我们可以给它们使用Symbol键:
letuser={//属于另一个代码 name:"John" }; letid=Symbol("id"); user[id]=1; alert(user[id]);//我们可以使用Symbol作为键来访问数据
在字符串"id"上使用Symbol("id")有什么好处?
因为user属于另一个代码,另一个代码也使用它执行一些操作,所以我们不应该在它上面加任何字段,这样很不安全。但是Symbol不会被意外访问到,所以第三方代码看不到它,所以使用Symbol也许不会有什么问题。
另外,假设另一个脚本希望在user中有自己的标识符,以实现自己的目的。这可能是另一个JavaScript库,因此脚本之间完全不了解彼此。
然后该脚本可以创建自己的Symbol("id"),像这样:
//... letid=Symbol("id"); user[id]="Theiridvalue";
我们的标识符和他们的标识符之间不会有冲突,因为Symbol总是不同的,即使它们有相同的名字。
……但如果我们处于同样的目的,使用字符串"id"而不是用symbol,那么就会出现冲突:
letuser={name:"John"}; //我们的脚本使用了"id"属性。 user.id="Ouridvalue"; //……另一个脚本也想将"id"用于它的目的…… user.id="Theiridvalue" //砰!无意中被另一个脚本重写了id!
字面量中的Symbol
如果我们要在对象字面量{...}中使用Symbol,则需要使用方括号把它括起来。
就像这样:
letid=Symbol("id"); letuser={ name:"John", [id]:123//而不是"id:123" };
这是因为我们需要变量id的值作为键,而不是字符串"id"。
Symbol在for..in中会被跳过
Symbolic属性不参与for..in循环。
例如:
letid=Symbol("id"); letuser={ name:"John", age:30, [id]:123 }; for(letkeyinuser)alert(key);//name,age(nosymbols) //使用Symbol任务直接访问 alert("Direct:"+user[id]);
Object.keys(user)也会忽略它们。这是一般“隐藏符号属性”原则的一部分。如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。
相反,Object.assign会同时复制字符串和symbol属性:
letid=Symbol("id"); letuser={ [id]:123 }; letclone=Object.assign({},user); alert(clone[id]);//123
这里并不矛盾,就是这样设计的。这里的想法是当我们克隆或者合并一个object时,通常希望所有属性被复制(包括像id这样的Symbol)。
其他类型的属性键被强制为字符串:
我们只能在对象中使用字符串或symbol作为键,其它类型会被转换为字符串。
例如,在作为属性键使用时,数字0变成了字符串"0":
letobj={ 0:"test"//和"0":"test"一样 }; //两个alert都访问相同的属性(Number0被转换为字符串"0") alert(obj["0"]);//test alert(obj[0]);//test(同一个属性)
全局symbol
正如我们所看到的,通常所有的Symbol都是不同的,即使它们有相同的名字。但有时我们想要名字相同的Symbol具有相同的实体。例如,应用程序的不同部分想要访问的Symbol"id"指的是完全相同的属性。
为了实现这一点,这里有一个全局Symbol注册表。我们可以在其中创建Symbol并在稍后访问它们,它可以确保每次访问相同名字的Symbol时,返回的都是相同的Symbol。
要从注册表中读取(不存在则创建)Symbol,请使用Symbol.for(key)。
该调用会检查全局注册表,如果有一个描述为key的Symbol,则返回该Symbol,否则将创建一个新Symbol(Symbol(key)),并通过给定的key将其存储在注册表中。
例如:
//从全局注册表中读取 letid=Symbol.for("id");//如果该Symbol不存在,则创建它 //再次读取(可能是在代码中的另一个位置) letidAgain=Symbol.for("id"); //相同的Symbol alert(id===idAgain);//true
注册表内的Symbol被称为全局Symbol。如果我们想要一个应用程序范围内的Symbol,可以在代码中随处访问——这就是它们的用途。
这听起来像Ruby:
在一些编程语言中,例如Ruby,每个名字都有一个Symbol。
正如我们所看到的,在JavaScript中,全局Symbol也是这样的。
Symbol.keyFor
对于全局Symbol,不仅有Symbol.for(key)按名字返回一个Symbol,还有一个反向调用:Symbol.keyFor(sym),它的作用完全反过来:通过全局Symbol返回一个名字。
例如:
//通过name获取Symbol letsym=Symbol.for("name"); letsym2=Symbol.for("id"); //通过Symbol获取name alert(Symbol.keyFor(sym));//name alert(Symbol.keyFor(sym2));//id
Symbol.keyFor内部使用全局Symbol注册表来查找Symbol的键。所以它不适用于非全局Symbol。如果Symbol不是全局的,它将无法找到它并返回undefined。
也就是说,任何Symbol都具有description属性。
例如:
letglobalSymbol=Symbol.for("name"); letlocalSymbol=Symbol("name"); alert(Symbol.keyFor(globalSymbol));//name,全局Symbol alert(Symbol.keyFor(localSymbol));//undefined,非全局 alert(localSymbol.description);//name
系统Symbol
JavaScript内部有很多“系统”Symbol,我们可以使用它们来微调对象的各个方面。
它们都被列在了众所周知的Symbol表的规范中:
- Symbol.hasInstance
- Symbol.isConcatSpreadable
- Symbol.iterator
- Symbol.toPrimitive
- ……等等。
例如,Symbol.toPrimitive允许我们将对象描述为原始值转换。我们很快就会看到它的使用。
当我们研究相应的语言特征时,我们对其他的Symbol也会慢慢熟悉起来。
总结
Symbol是唯一标识符的基本类型
Symbol是使用带有可选描述(name)的Symbol()调用创建的。
Symbol总是不同的值,即使它们有相同的名字。如果我们希望同名的Symbol相等,那么我们应该使用全局注册表:Symbol.for(key)返回(如果需要的话则创建)一个以key作为名字的全局Symbol。使用Symbol.for多次调用key相同的Symbol时,返回的就是同一个Symbol。
Symbol有两个主要的使用场景:
- “隐藏”对象属性。如果我们想要向“属于”另一个脚本或者库的对象添加一个属性,我们可以创建一个Symbol并使用它作为属性的键。Symbol属性不会出现在for..in中,因此它不会意外地被与其他属性一起处理。并且,它不会被直接访问,因为另一个脚本没有我们的symbol。因此,该属性将受到保护,防止被意外使用或重写。
因此我们可以使用Symbol属性“秘密地”将一些东西隐藏到我们需要的对象中,但其他地方看不到它。
- JavaScript使用了许多系统Symbol,这些Symbol可以作为Symbol.*访问。我们可以使用它们来改变一些内置行为。例如,在本教程的后面部分,我们将使用Symbol.iterator来进行迭代操作,使用Symbol.toPrimitive来设置对象原始值的转换等等。
从技术上说,Symbol不是100%隐藏的。有一个内置方法Object.getOwnPropertySymbols(obj)允许我们获取所有的Symbol。还有一个名为Reflect.ownKeys(obj)的方法可以返回一个对象的所有键,包括Symbol。所以它们并不是真正的隐藏。但是大多数库、内置方法和语法结构都没有使用这些方法。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。