你知道JavaScript Symbol类型怎么用吗


Posted in Javascript onJanuary 08, 2020

Symbol 类型

根据规范,对象的属性键只能是字符串类型或者 Symbol 类型。不是 Number,也不是 Boolean,只有字符串或 Symbol 这两种类型。

到目前为止,我们只见过字符串。现在我们来看看 Symbol 能给我们带来什么好处。

Symbol

"Symbol" 值表示唯一的标识符。

可以使用 Symbol() 来创建这种类型的值:

// id 是 symbol 的一个实例化对象
let id = Symbol();

创建时,我们可以给 Symbol 一个描述(也称为 Symbol 名),这在代码调试时非常有用:

// id 是描述为 "id" 的 Symbol
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); // 类型错误:无法将 Symbol 值转换为字符串。

这是一种防止混乱的“语言保护”,因为字符串和 Symbol 有本质上的不同,不应该意外地将它们转换成另一个。

如果我们真的想显示一个 Symbol,我们需要在它上面调用 .toString(),如下所示:

let id = Symbol("id");
alert(id.toString()); // Symbol(id),现在它有效了

或者获取 symbol.description 属性,只显示描述(description):

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

“隐藏”属性

Symbol 允许我们创建对象的“隐藏”属性,代码的任何其他部分都不能意外访问或重写这些属性。

例如,如果我们使用的是属于第三方代码的 user 对象,我们想要给它们添加一些标识符。

我们可以给它们使用 Symbol 键:

let user = { // 属于另一个代码
 name: "John"
};

let id = Symbol("id");

user[id] = 1;

alert( user[id] ); // 我们可以使用 Symbol 作为键来访问数据

在字符串 "id" 上使用 Symbol("id") 有什么好处?

因为 user 属于另一个代码,另一个代码也使用它执行一些操作,所以我们不应该在它上面加任何字段,这样很不安全。但是 Symbol 不会被意外访问到,所以第三方代码看不到它,所以使用 Symbol 也许不会有什么问题。
另外,假设另一个脚本希望在 user 中有自己的标识符,以实现自己的目的。这可能是另一个 JavaScript 库,因此脚本之间完全不了解彼此。

然后该脚本可以创建自己的 Symbol("id"),像这样:

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

user[id] = "Their id value";

我们的标识符和他们的标识符之间不会有冲突,因为 Symbol 总是不同的,即使它们有相同的名字。

……但如果我们处于同样的目的,使用字符串 "id" 而不是用 symbol,那么 就会 出现冲突:

let user = { name: "John" };

// 我们的脚本使用了 "id" 属性。
user.id = "Our id value";

// ……另一个脚本也想将 "id" 用于它的目的……  

user.id = "Their id value"
// 砰!无意中被另一个脚本重写了 id!

字面量中的 Symbol

如果我们要在对象字面量 {...} 中使用 Symbol,则需要使用方括号把它括起来。

就像这样:

let id = Symbol("id");

let user = {
 name: "John",
 [id]: 123 // 而不是 "id:123"
};

这是因为我们需要变量 id 的值作为键,而不是字符串 "id"。

Symbol 在 for..in 中会被跳过

Symbolic 属性不参与 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)

// 使用 Symbol 任务直接访问
alert( "Direct: " + user[id] );

Object.keys(user) 也会忽略它们。这是一般“隐藏符号属性”原则的一部分。如果另一个脚本或库遍历我们的对象,它不会意外地访问到符号属性。

相反,Object.assign 会同时复制字符串和 symbol 属性:

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

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

alert( clone[id] ); // 123

这里并不矛盾,就是这样设计的。这里的想法是当我们克隆或者合并一个 object 时,通常希望 所有 属性被复制(包括像 id 这样的 Symbol)。

其他类型的属性键被强制为字符串:

我们只能在对象中使用字符串或 symbol 作为键,其它类型会被转换为字符串。

例如,在作为属性键使用时,数字 0 变成了字符串 "0":

let obj = {
 0: "test" // 和 "0": "test" 一样
};

// 两个 alert 都访问相同的属性(Number 0 被转换为字符串 "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 将其存储在注册表中。

例如:

// 从全局注册表中读取
let id = Symbol.for("id"); // 如果该 Symbol 不存在,则创建它

// 再次读取(可能是在代码中的另一个位置)
let idAgain = 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
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// 通过 Symbol 获取 name
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id

Symbol.keyFor 内部使用全局 Symbol 注册表来查找 Symbol 的键。所以它不适用于非全局 Symbol。如果 Symbol 不是全局的,它将无法找到它并返回 undefined。

也就是说,任何 Symbol 都具有 description 属性。

例如:

let globalSymbol = Symbol.for("name");
let localSymbol = 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。所以它们并不是真正的隐藏。但是大多数库、内置方法和语法结构都没有使用这些方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 面向对象思想 附源码
Jul 07 Javascript
jquery validation插件表单验证的一个例子
Mar 03 Javascript
同一页面多个商品倒计时JS 基于面向对象的javascript
Feb 16 Javascript
JavaScript实现查找字符串中第一个不重复的字符
Dec 29 Javascript
JQuery调用绑定click事件的3种写法
Mar 28 Javascript
jQuery实现查找链接文字替换属性的方法
Jun 27 Javascript
如何提高Dom访问速度
Jan 05 Javascript
Node.js中.pfx后缀文件的处理方法
Mar 10 Javascript
在Vue中使用highCharts绘制3d饼图的方法
Feb 08 Javascript
vue+elementUI实现表单和图片上传及验证功能示例
May 14 Javascript
解决VUEX的mapState/...mapState等取值问题
Jul 24 Javascript
vue3+typeScript穿梭框的实现示例
Dec 29 Vue.js
js实现多个标题吸顶效果
Jan 08 #Javascript
Vue v-model组件封装(类似弹窗组件)
Jan 08 #Javascript
jquery实现吸顶导航效果
Jan 08 #jQuery
JS实现网站吸顶条
Jan 08 #Javascript
js实现移动端吸顶效果
Jan 08 #Javascript
vue.js自定义组件实现v-model双向数据绑定的示例代码
Jan 08 #Javascript
微信小程序实现吸顶效果
Jan 08 #Javascript
You might like
CentOS 6.2使用yum安装LAMP以及phpMyadmin详解
2013/06/17 PHP
ThinkPHP让分页保持搜索状态的方法
2014/07/02 PHP
PHP基于DOMDocument解析和生成xml的方法分析
2017/07/17 PHP
php大小写转换函数(strtolower、strtoupper)用法介绍
2017/11/17 PHP
jquery下实现overlay遮罩层代码
2010/08/25 Javascript
自己写的兼容ie和ff的在线文本编辑器类似ewebeditor
2012/12/12 Javascript
jQuery侧边栏随窗口滚动实现方法
2013/03/04 Javascript
JS中获取函数调用链所有参数的方法
2015/05/07 Javascript
JavaScript简单下拉菜单实例代码
2015/09/07 Javascript
微信小程序 传值取值的几种方法总结
2017/01/16 Javascript
基于jquery实现多级菜单效果
2017/07/25 jQuery
vue实现axios图片上传功能
2019/08/20 Javascript
JS中FileReader类实现文件上传及时预览功能
2020/03/27 Javascript
Python中的闭包实例详解
2014/08/29 Python
详解使用Python处理文件目录的相关方法
2015/10/16 Python
python生成验证码图片代码分享
2016/01/28 Python
python Django批量导入数据
2016/03/25 Python
wxpython中Textctrl回车事件无效的解决方法
2016/07/21 Python
一个基于flask的web应用诞生 flask和mysql相连(4)
2017/04/11 Python
python通过opencv实现批量剪切图片
2017/11/13 Python
利用信号如何监控Django模型对象字段值的变化详解
2017/11/27 Python
python3爬虫怎样构建请求header
2018/12/23 Python
python实现石头剪刀布程序
2021/01/20 Python
tensorflow通过模型文件,使用tensorboard查看其模型图Graph方式
2020/01/23 Python
Django查询优化及ajax编码格式原理解析
2020/03/25 Python
python实现udp聊天窗口
2020/03/31 Python
python下对hsv颜色空间进行量化操作
2020/06/04 Python
CSS3中新增的对文本和字体的设置
2020/02/03 HTML / CSS
使用HTML5中的contentEditable来将多行文本自动增高
2016/03/01 HTML / CSS
介绍一下grep命令的使用
2012/06/28 面试题
感恩教育观后感
2015/06/17 职场文书
多属性、多分类MySQL模式设计
2021/04/05 MySQL
React Hook用法示例详解(6个常见hook)
2021/04/28 Javascript
pytorch DataLoader的num_workers参数与设置大小详解
2021/05/28 Python
dubbo服务整合zipkin详解
2021/07/26 Java/Android
python自动化测试之Selenium详解
2022/03/13 Python