• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

4.3 Symbol type(标记类型)

JS 教程 tiantian 185次浏览 0个评论 扫描二维码

根据规范,对象的属性键可能是 string(字符串) 类型,也可能是 symbol(标记) 类型。不能是 number,也不能是 boolean,只可能为字符串或者标记类型。

目前为止,我们只学过了键的类型为字符串的对象。现在,让我们来看看标记类型的优势。

Symbol

“Symbol”值表示一个独一无二的标记符。

我们可以使用 Symbol() 来创建一个此种类型的值:

// id is a new symbol
let id = Symbol();

此外,我们也可以给 symbol 一个描述,或者称为一个 symbol 名称,在调试代码时作用很大:

// id is a symbol with the description "id"
let id = Symbol("id");

Symbol 都是独一无二的。即使我们使用相同的描述来创建多个 symbol,它们也是不同的值。那个描述知识一个标记,并不能产生任何影响。

举个例子,此处有两个 symbol,它们具有相同的描述。但是,它们仍然不相等:

let id1 = Symbol("id");
let id2 = Symbol("id");

alert(id1 == id2); // false

如果你熟悉 Ruby 或者其他的语言,它们具有某种名为的 “symbol”的语法,请注意,不要混淆它们。JavaScript 的 symbol 与它们不一样。

Symbol不会自动转换成字符串

JavaScript 中的绝大部分值都可以隐式地转换成字符串。举个例子,我们可以使用alert来显示任何值,而且都能正常工作。Symbol 是特殊的。它们不会自动转换。

举个例子,下列的alert将显示一个错误:

let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a string

如果我们的确想要显示一个 symbol,我们需要在其上调用.toString()函数,比如:

let id = Symbol("id");
alert(id.toString()); // Symbol(id), now it works

这是为了防止类型混淆的”语言层级“的保障,因为 string 和 symbol 是两个从根本上就不同的类型,所以它们不应该可以隐式转换。

“Hidden” properties(隐藏属性)

Symbol 允许我们在对象中创建“hidden”属性,这样的属性不会被其他的代码在无意间访问和修改。

举个例子,如果我们期望在user对象中存储一个标记,我们可以使用一个 symbol 作为该标记的键值:

let user = { name: "John" };
let id = Symbol("id");

user[id] = "ID Value";
alert( user[id] ); // we can access the data using the symbol as the key

那么问题来了,使用Symbol("id"),而不是用字符串"id"的好处是什么呢?

现在,让我们来更加深入地学习该示例代码。

假如,现在,为了实现它自己的目的,另外一个脚本想要往该对象user中添加一个它自己的”id”属性,。这个情况可能是另外一个 JavaScript 库,所以,当前脚本可能与那个脚本互相不知道对方的存在。

此时,那个脚本就可以创建它自己的Symbol("id")库,比如:

// ...
let id = Symbol("id");

user[id] = "Their id value";

这样的操作不会产生任何冲突,因为 symbol 总是不同值,即使它们具有相同的名称。

注意,如果我们使用一个字符串"id"来代替上述的示例中 symbol,希望完成同样的目的,那么就会导致一个冲突:

let user = { name: "John" };

// our script uses "id" property
user.id = "ID Value";

// ...if later another script the uses "id" for its purposes...

user.id = "Their id value"
// boom! overwritten! it did not mean to harm the colleague, but did it!

字面量 Symbol

如果我们期望在一个字面量对象中使用 symbol,我们需要使用方括号语法。

比如:

let id = Symbol("id");

let user = {
  name: "John",
  [id]: 123 // not just "id: 123"
};

这是因为我们需要获取变量id中的 symbol 值作为对象的键值,而不是字符串”id”。

Symbol 被 for…in 忽略

Symbol 属性不会参与到for ... in 循环中。

举个例子:

let id = Symbol("id");
let user = {
  name: "John",
  age: 30,
  [id]: 123
};

for (let key in user) alert(key); // name, age (no symbols)

// the direct access by the symbol works
alert( "Direct: " + user[id] );

这也是隐藏属性概念的部分内容。如果另外一个脚本或者代码库迭代我们的对象,它就不会在无意间访问到一个 symbol 类型的属性。

但是,Object.assign 会复制字符串和symbol类型的属性:

let id = Symbol("id");
let user = {
  [id]: 123
};

let clone = Object.assign({}, user);

alert( clone[id] ); // 123

当然,这个语法并没有什么不妥当的。这是语言设计者全面考虑后的选择。背后的原因是:当我们克隆一个对象,或者合并对象的时候,我们通常期望该对象的所有属性都被克隆,这当然也应该包括 symbol属性。

属性键的类型会自动协变为字符串类型

对象的键的类型只能是字符串类型或者symbol类型。其他的类型会协变成字符串类型。

举个例子,一个数字0会协变成一个字符串"0",当使用该数字作为一个属性键时:

let obj = {
 0: "test" // same as "0": "test"
};

// both alerts access the same property (the number 0 is converted to string "0")
alert( obj["0"] ); // test
alert( obj[0] ); // test (same property)

全局 Symbol

正如我们所知,所有的 symbol 都是互不相同的,即使它们具有相同的名称。但是,我们希望那些相同名称的 symbol 是相同的 symbol 实体。

举个例子,我们的应用程序的不同部分希望访问一个名为"id"的 symbol时,它们访问的是同一个 symbol 值。

为了实现这个功能,JavaScript 中存在一个全局 symbol 注册处。我们可以在其中创建 symbol,并在后面的代码中访问该 symbol,并且保证使用相同的名称进行充分访问的情况下,会返回同一个 symbol 值。

为了在那个全局的注册处中创建或者获取 symbol,需要使用Symbol.for(key)

那个调用会检查全局Symbol注册处,如果已经存在一个名为key的 symbol,那么就返回该 symbol,否则的话,就创建一个新的 symbol Symbol(key),并以key作为键值,将那个新建的 symbol 存储于全局注册处。

举个例子:

// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created

// read it again
let idAgain = Symbol.for("id");

// the same symbol
alert( id === idAgain ); // true

位于全局注册处的 Symbol 被称为全局Symbol。如果我们期望一个应用范围内的 symbol,意味着在应用的任何地方都可以被访问,我们就可以使用全局Symbol。

Symbol.keyFor

对于全局symbol而言,不仅仅Symbol.for(key)会依据一个名称来返回一个symbol,而且还存在一个相反功能的函数:Symbol.keyFor(sym),具有相反的功能:返回全局symbol的的名称。

举个例子:

let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// get name from symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id

实际上,那个Symbol.keyFor在内部会使用全局注册处来查找参数symbol的键值。所以,这个函数对于非全局的Symbol是不适用的。如果那个symbol不是全局的,该函数将无法发现其名称,所以就返回undefined

举个例子:

alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol

alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol

系统Symbol

JavaScript 语言本身使用了非常多的“系统“Symbol来实现其某些功能,而且我们可以使用它们来很好地xi修改对象的多个方面的功能。

下面,我们列举几个在规范中被列为著名symbol 的symbol:

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.toPrimitive
  • 等等

举个例子,Symbol.toPrimitive允许我们描述对象到原始类型的转换。我们将很快看到如何使用该 Symbol。

当然,其他的Symbol也会在我们学习相关语言特性的过程中被我们所熟悉。

总结

Symbol是一种原始类型,用来作为独一无二的标记符。

Symbol 是通过 Symbol()来进行创建的,可以具有一个可选的的描述符。

Symbol 总是不同的值,即使它们具有相同的名称。如果我们希望具有相同名称的 symbol 就是相同的值,那么我们可以使用全局注册处:Symbol.for(key) 来返回(在需要的时候创建)一个全局的 symbol,它的key就是其名称。对于Symbol.for的多次调用返回的是同一个 symbol 值。

Symbol 具有两个最重要的用法:

  1. “隐藏”对象属性。如果某个对象“属于”另一个脚本或者库,而我们希望往其中添加一个属性,那么我们就可以创建一个 symbol,并将其作为一个属性的键值。一个 symbol 类型的属性是不会出现在对象的for...in循环中,所以该属性不会被无意中被列举出来。同时,它也不会被直接访问,因为另一个脚本没有该 symbol 的引用,所以无法访问它并干预到脚本的行为。
  2. JavaScript 内部使用了非常多的系统 symbol,它们可以使用 Symbol.* 来进行访问。我们也可以使用它们来修改内建的行为。举个例子,在本教程的后面示例中,我们将看到使用Symbol.iterator来进行迭代 iterabels,使用 Symbol.toPrimitive来设置对象到原始类型的转换 等功能。

仅技术而言,symbol 不是 100% 隐藏的。存在一个内建的方法Object.getOwnProperySymbols(obj),允许我们获取对象的所有 symbol。此外,还存在一个名为Reflect.ownKeys(obj)的方法,可以用来返回对象的所有键,包括 symbol 类型的键。所以,symbol 并不是真正的隐藏。但是,对于绝大多数的库,内建方法和那些遵守通用规则的那些语法结构而言,symbol 就是隐藏的。而那些调用前面两个特殊方法的库或者函数,肯定非常清楚上述两个方法的行为。


天天编码 , 版权所有丨本文标题:4.3 Symbol type(标记类型)
转载请保留页面地址:http://www.tiantianbianma.com/symbol-type.html/
喜欢 (0)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址