你知道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 无符号右移运算符
Apr 17 Javascript
让你的博客飘雪花超出屏幕依然看得见
Jan 04 Javascript
jquery实现多级下拉菜单的实例代码
Oct 02 Javascript
jQuery+CSS3实现树叶飘落特效
Feb 01 Javascript
JavaScript实现DIV层拖动及动态增加新层的方法
May 12 Javascript
详解js跨域原理以及2种解决方案
Dec 09 Javascript
JS基础随笔(菜鸟必看篇)
Jul 13 Javascript
微信小程序 教程之小程序配置
Oct 17 Javascript
分享19个JavaScript 有用的简写写法
Jul 07 Javascript
原生JS检测CSS3动画是否结束的方法详解
Jan 27 Javascript
JS实现的进制转换,浮点数相加,数字判断操作示例
Nov 09 Javascript
微信小程序返回上一级页面的实现代码
Jun 19 Javascript
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
用PHP实现文件上传二法
2006/10/09 PHP
PHP stristr() 函数(不区分大小写的字符串查找)
2010/06/03 PHP
php中数组首字符过滤功能代码
2012/07/31 PHP
php中实现记住密码下次自动登录的例子
2014/11/06 PHP
PHP计算百度地图两个GPS坐标之间距离的方法
2015/01/09 PHP
Codeigniter校验ip地址的方法
2015/03/21 PHP
js实现a标签超链接提交form表单的方法
2015/06/24 Javascript
Java遍历集合方法分析(实现原理、算法性能、适用场合)
2016/04/25 Javascript
npm国内镜像 安装失败的几种解决方案
2017/06/04 Javascript
jQuery实现火车票买票城市选择切换功能
2017/09/15 jQuery
Angular实现下拉框模糊查询功能示例
2018/01/03 Javascript
JavaScript继承的特性与实践应用深入详解
2018/12/30 Javascript
[02:46]2014DOTA2国际邀请赛 选手为你解读比赛MVP充满梦想
2014/07/09 DOTA
Python 自动补全(vim)
2014/11/30 Python
Python 中的with关键字使用详解
2016/09/11 Python
python中的计时器timeit的使用方法
2017/10/20 Python
Python(Django)项目与Apache的管理交互的方法
2018/05/16 Python
python整合ffmpeg实现视频文件的批量转换
2019/05/31 Python
python实现截取屏幕保存文件,删除N天前截图的例子
2019/08/27 Python
python实现超市商品销售管理系统
2019/10/25 Python
python爬虫使用requests发送post请求示例详解
2020/08/05 Python
python Matplotlib模块的使用
2020/09/16 Python
不用游标的SQL语句有哪些
2012/09/07 面试题
Javascript如何发送一个Ajax请求
2015/01/26 面试题
介绍一下JNDI的基本概念
2013/07/26 面试题
幼师自荐信范文
2013/10/06 职场文书
领导接待方案
2014/03/13 职场文书
班主任新年寄语
2014/04/04 职场文书
软件项目实施计划书
2014/05/02 职场文书
公安学专业求职信
2014/07/27 职场文书
优秀班集体事迹材料
2014/12/25 职场文书
2015年大学班主任工作总结
2015/04/30 职场文书
指导教师推荐意见
2015/06/05 职场文书
Java数组与堆栈相关知识总结
2021/06/29 Java/Android
spring boot中nativeQuery的用法
2021/07/26 Java/Android
MySQL自定义函数及触发器
2022/08/05 MySQL