引子
本文为【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 Boolean、new String以及new Number,因为遗留原因仍可被创建,如下图所示:
如果你确实想使用符合包装对象,可以借用 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
返回值: 返回全局注册表中查找到的 symbol 的key 值,返回值为字符串类型;若查询的不是全局符号或找不到,则返回 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.defineProperty 与 Object.defineProperties
其他方法
Object.getOwnPropertyNames() 返回对象实例的常规属性数组,
Object.getOwnPropertySymbols() 返回对象实例的符号属性数组,这两个方法的返回值彼此互斥;
Object.getOwnPropertyDescriptors() 会返回同时包含常规和符号属性描述符的对象;Reflect.ownKeys()则会返回两种类型的键。
如下图示例:
注意点
因为符号属性是对内存中符号的一个引用,所以直接创建并用作属性的符号不会丢失;但如果没有显式地保存对这些属性的引用,那么必须遍历对象的所有符号属性才能找到相应的属性键;
如下图所示:
🥚ps: find() 方法返回数组中满足提供的测试函数的第一个元素的值
match() 方法检索返回一个字符串匹配正则表达式的结果
总结
Symbol是原始数据类型,用来定义对象的唯一属性名;
Symbol()函数会返回 Symbol 类型的值;Symbol()函数中的字符串参数(描述)与符号定义或标识完全无关- 只要创建
Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性 Symbol.for(key)用于在全局注册表中检测与添加该字符串参数 Symbol 值Symbol.keyFor(key)用来检测该字符串参数作为名称的 Symbol 值是否已被登记- 凡是可以使用字符串或数值作为属性的地方,都可以使用符号
\