【JS红宝书⁰⁸】数据类型之Symbol类型

492 阅读6分钟

引子

本文为【JSRedBook】中数据类型的篇章, 主要讲述 ECMAScript 的 6 种简单数据类型(原始类型)中的其中一种:Symbol;主要说明三个方面:符号基本用法使用全局注册表使用符号作为元素;因为本文对于入门有点超纲,所以大多代码用图片示例;

Symbol类型

Symbol(符号)是 ECMAScript 6 新增的数据类型;

定义: 符号是原始值,且符号实例是唯一,不可变的

语法: Symbol([description]) 可选,参数为字符串类型,为对symbol的描述

作用: 确保对象属性使用唯一标识符,不会发生属性冲突的危险

符号基本用法

符号需要使用Symbol()函数初始化,因为符号本身是原始类型,所以 typeof 操作符对符号返回 symbol

基础用法

我们来看下面这个例子:

 let testSymbol = Symbol()
 console.log(typeof testSymbol) // 'symbol'

传参用法

调用 Symbol()函数时,也可以传入一个字符串参数作为对符号的描述(description),将来可以通过这个字符串来调试代码;

但是,这个字符串参数与符号定义或标识完全无关,如下:

 let a = Symbol()
 let b = Symbol()
 let c = Symbol("测试")
 let d = Symbol("测试")
 console.log(c) // symbol(测试)
 console.log(typeof a); // "symbol"
 console.log(a==b);  // false
 console.log(c==d);  // false

按照规范,你只要创建 Symbol() 实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性,如下:

 let genericSymbol = Symbol(); 
 console.log(genericSymbol); // Symbol() 
 let fooSymbol = Symbol('foo'); 
 console.log(fooSymbol); // Symbol(foo); 

Symbol不能使用new

Symbol()函数不能与 new 关键字一起作为构造函数使用

围绕原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持; 然而,现有的原始包装器对象,如 new Booleannew String以及new Number,因为遗留原因仍可被创建,如下图所示:

Symbol

如果你确实想使用符合包装对象,可以借用 Object() 函数:

 let mySymbol = Symbol("test");
 console.log(typeof mySymbol); // "symbol"
 let oneSymbol = Object(mySymbol);
 console.log(typeof oneSymbol); // "object"

使用全局符号注册表

Symbol()函数并不会在你整个代码库中创建一个可用的全局的symbol类型;若要创建跨文件的symbol或是跨域,可以使用 Symbol.for()Symbol.keyFor() 方法从全局的symbol注册表设置和取得symbol

Symbol.for()

Symbol.for()方法会对每个字符串键都执行幂等操作,首先会根据给定的键,来从运行时 symbol 注册表中找到对应的 symbol;如果找到了对应的符合,则返回它,如果发现不存在对应的符合,就会生成一个新符合实例并添加到注册表中。

语法: Symbol.for(key);

参数: 字符串,作为 symbol 注册表中与某 symbol 关联的键

返回值: 返回由给定的 key 找到的 symbol,否则就是返回新创建的 symbol

使用示例

如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键在全局符号注册表中创建并重用符号,这里我们使用 Symbol.for(),如下:

 let test = Symbol.for('foo');
 console.log(test) // "Symbol(foo)"
 console.log(typeof test) // symbol

全局注册表中的符号必须使用字符串键来创建,因此作为参数传给 Symbol.for()的任何值都会被转换为字符串;此外,注册表中使用的键同时也会被用作符号描述;如下所示:

 let emptyGlobalSymbol = Symbol.for();  // 没传参数
 console.log(emptyGlobalSymbol);  // Symbol(undefined) 

使用相同时的规范

使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例;如下所示:

 let createSymbol = Symbol.for('test');  // 创建新符号
 let reuseSymbol = Symbol.for('test');   // 重用已有符号
 console.log(createSymbol === reuseSymbol);  // true

即使采用相同的符号描述,在全局注册表中定义的符号跟使用 Symbol()定义的符号也并不等同,如下所示:

 let localSymbol = Symbol("test");  // 局部
 let globalSymbol = Symbol.for("test");  // 全局
 console.log(localStorage === globalSymbol);  //false

Symbol.keyFor()

Symbol.keyFor()方法用来获取symbol 注册表中与某个 symbol 关联的键

语法: Symbol.keyFor(sym)

参数: 必选 符号,为需要查找键值的 Symbol

返回值: 返回全局注册表中查找到的 symbolkey 值,返回值为字符串类型;若查询的不是全局符号或找不到,则返回 undefined

使用示例

具体使用与 Symbol.for() 大同小异:

 // 创建普通符号
 let test = Symbol('good'); 
 console.log(Symbol.keyFor(test)); // undefined 
 ​
 // 创建全局符号
 let globaltesst = Symbol.for('good'); 
 console.log(Symbol.keyFor(globaltesst)); // good 
小注意

如果传给 Symbol.keyFor()的不是符号,则该方法抛出 TypeError:

 Symbol.keyFor(123);  // TypeError: 123 is not a symbol 

如果传给 Symbol.keyFor()的不保存在全局Symbol注册表中,则该方法返回 undefined:

 Symbol.keyFor(Symbol.testnot);  // undefined

使用符号作为元素

凡是可以使用字符串或数值作为属性的地方,都可以使用符号

包括对象字面量属性Object.defineProperty()/ Object.defineProperties()定义的属性,对象字面量只能在计算属性语法中使用符号作为属性;

搭配使用

Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象;Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象

用图片解析会更清晰,如下图所示:使用符号作为元素

这里不做详细介绍,想了解具体可以查看这个 Object.definePropertyObject.defineProperties

其他方法

Object.getOwnPropertyNames() 返回对象实例的常规属性数组,

Object.getOwnPropertySymbols() 返回对象实例的符号属性数组,这两个方法的返回值彼此互斥

Object.getOwnPropertyDescriptors() 会返回同时包含常规和符号属性描述符的对象;Reflect.ownKeys()则会返回两种类型的键。

如下图示例:

Symbol其他方法使用

注意点

因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失;但如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键;

如下图所示:

🥚ps: find() 方法返回数组中满足提供的测试函数的第一个元素的值

match() 方法检索返回一个字符串匹配正则表达式的结果

总结

Symbol是原始数据类型,用来定义对象的唯一属性名;

  • Symbol()函数会返回 Symbol 类型的值;
  • Symbol()函数中的字符串参数(描述)与符号定义或标识完全无关
  • 只要创建 Symbol() 实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性
  • Symbol.for(key)用于在全局注册表中检测添加该字符串参数 Symbol
  • Symbol.keyFor(key) 用来检测该字符串参数作为名称的 Symbol 值是否已被登记
  • 凡是可以使用字符串或数值作为属性的地方,都可以使用符号

\