JS - Symbol 类型的基本使用和获取对象的键的方法

319 阅读4分钟

Symbol(符号)类型是原始值,通过 Symbol 创建的符号是唯一、不可变的,可用作非字符串形式的对象属性,保证对象属性使用的是唯一标识符,不会发生属性冲突的危险。

说明:文章中所有 log 等效于 console.log

Symbol 的基本使用

symbol 是 ES6 新增的原始类型,通过 Symbol() 函数初始化,Symbol 不是构造函数,(因此不能 new)

Symbol 的类型声明

Symbol(description?: string | number | undefined): symbol Symbol 函数接受一个描述符,描述符的 类型 可以是 string,number,undefined 中的一种,可以根据类型声明来创建一个字符,这里的 descripton 可以说和 定义的 symbol 并没有太大的关系(即相同的 description 创建的 symbol 也是不相等的

const sym = Symbol();
const symStr1 = Symbol('任何描述符都OK');
const symStr2 = Symbol('任何描述符都OK');
const symNum = Symbol(123);

log(symStr1); // log: Symbol(任何描述符都OK)
log(typeof symStr1); // log: symbol

// 相同的描述符 创建的 symbol 也是不相等的哟~
log(symStr2 === symStr1); // false
log(symStr2 == symStr1); // false

// 错误方式
// const err = new Symbol(); // ❌ TypeError: Symbol is not a constructor

使用全局符号注册表 Symbol.for(description)

如果运行时不同的部分需要共享和复用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并复用符号;

Symbol.for(str) 对每个字符串都执行幂等的操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号才会生成一个新的符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,就会直接返回该字符号实例

const globalSymbol1 = Symbol.for("keyStr");
const globalSymbol2 = Symbol.for("keyStr");

const { log } = console;
// 使用 Symbol.for 可以复用字符,Symbol.for(key:string) 传递的 key 相同,返回值就是相同的,即 key 和 返回的 symbol 永远一对一
log(globalSymbol1 === globalSymbol2);
log(globalSymbol1); // Symbol(keyStr)
log(globalSymbol2); // Symbol(keyStr)

/***************** 模拟 Symbol.for 的实现 START *********************/
const globalRegedit = {};
const isNil = (v) => v === undefined || v === null;

// 实现 Symbol.for(str) 是幂等操作
const _Symbol = {
  for(key) {
    // 如果 globalRegedit[key] 没有值,创建一个 symbol,如果有值 直接返回
    if (isNil(globalRegedit[key])) {
      globalRegedit[key] = Symbol(key);
    }
    
    return globalRegedit[key];
  },
};

const sfor1 = _Symbol.for("keyStr");
const sfor2 = _Symbol.for("keyStr");
log(sfor1 === sfor2);
log(sfor1);
/***************** 模拟 Symbol.for 的实现 END *********************/

使用全局注册表定义的符号 跟使用 Symbol 定义的符号也是不相同的

Symbol(description) 主打的就是创建唯一的符号,即是 description 是一样的其每次调用的返回的 字符都是唯一的

重点: Symbol(description) 不是幂等的、Symbol.for(key) 是幂等的

const s1 = Symbol("1"); // s1 是唯一的
const s2 = Symbol("1"); // s2 也是唯一的 符号,就算它的 description 和 s1 一样
log(s1 === s2); // flase

Symbol.keyFor(sym:symbol) 查询全局注册表

const { log } = console;

const globalSymbol1 = Symbol.for("keyStr"); // 创建全局符号
const sym1 = Symbol("1"); // 非全局 符号

log(Symbol.keyFor(globalSymbol1)); // keyStr
log(Symbol.keyFor(sym1)); // undefined, 因为 没有创建使用 Symbol.for("1") 创建 sym1
log(Symbol.keyFor(123)); // TypeError keyFor 如果接受的不是 symbol 类型的,则会报错

使用符号作为属性

凡是可以使用字符串或数值的作为属性的地方,都可以使用符号。对象字面量只能在计算属性语法中使用符号作为属性。

const { log } = console;
const key1 = Symbol("key1");

const obj = {
  [key1]: "jakquc",
  key1: "native",
};

log(obj); // { key1: 'native', [Symbol(key1)]: 'jakquc' }

Object 提供获取属性的方法:

  • Object.getOwnPropertyNames(obj:any) 获取 obj 的非 symbol 类型的属性数组;
  • Object.getOwnPropertySymbols(obj:any) 来获取 所有 obj 的 symbol 类型属性的字符数组
  • 获取 obj 的所有属性 可以直接使用 Reflect.ownKeys(obj)
  • Object.keys(obj)只能获取 可枚举的非 symbol 类型的属性
const { log } = console;
const k1 = Symbol("k1");
const k2 = Symbol("k2");

const obj = {
  [k1]: "symK1Content",
  k1: "normalK1",
  [k2]: "symK1Content",
  k2: "k2",
  1: 1,
  ["a-1"]: "a-1",
  method() {},
};

// 将常规属性 k1, k2 定义为 不可枚举
Object.defineProperties(obj, {
  k1: { enumerable: false },
  k2: { enumerable: false },
});

// 获取 obj 自己的所有 常规属性(非 symbol 类型的属性)
log(Object.getOwnPropertyNames(obj)); // log: [ '1', 'k1', 'k2', 'a-1', 'method' ]

// 获取 obj 自己的所有 symbol 类型的属性
log(Object.getOwnPropertySymbols(obj)); // log: [ Symbol(k1), Symbol(k2) ]; 获取 obj 对象的 symbol 属性数组

// keys 方法只能获取 可枚举 的常规属性
log(Object.keys(obj)); // log: [ '1', 'a-1', 'method' ]

// ownKeys 方法获取 obj 的所有属性 (也包含了可枚举、不可枚举、常规属性、symbol属性)
log(Reflect.ownKeys(obj)); // log:  [ '1', 'k1', 'k2', 'a-1', 'method', Symbol(k1), Symbol(k2) ]