ES6-Symbol-2

310 阅读4分钟

背景

ES6 引入了一种新的基本数据类型 Symbol,表示独一无二的值。而且 Symbol 的内容比较多,作为个人也学习了两天左右,所以才有了这篇,对于 Symbol 更多的介绍,上一篇基础介绍可以查看ES6-Symbol。如果想一次看详细内容,可以阅读阮一峰大神关于 Symbol的讲解,本文很多都是参照和 copy,然后加上个人实验,想详细了解,可以查看大神的讲解。

属性名的遍历

Symbol 作为属性名,该属性不会出现在 for...infor...of 循环中,也不会被 Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols 方法,可以获取指定对象的所有 Symbol 属性名。

Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

Symbol示例

从面示例可以看出使用 Object.getOwnPropertyNames 方法得不到 Symbol 属性名,需要使用 Object.getOwnPropertySymbols 方法。

另一个新的 API,Reflect.ownKeys 方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。

let obj = {
  [Symbol("my_key")]: 1,
  enum: 2,
  nonEnum: 3
};

Reflect.ownKeys(obj);
//  ["enum", "nonEnum", Symbol(my_key)]

由于以 Symbol 值作为名称的属性,不会被常规方法遍历得到。我们可以利用这个特性,为对象定义一些非私有的、但又希望只用于内部的方法。

let size = Symbol("size");

class Collection {
  constructor() {
    this[size] = 0;
  }

  add(item) {
    this[this[size]] = item;
    this[size]++;
  }

  static sizeOf(instance) {
    return instance[size];
  }
}

let x = new Collection();
Collection.sizeOf(x); // 0

x.add("foo");
Collection.sizeOf(x); // 1

Object.keys(x); // ['0']
Object.getOwnPropertyNames(x); // ['0']
Object.getOwnPropertySymbols(x); // [Symbol(size)]

上面代码中,对象 xsize 属性是一个 Symbol 值,所以 Object.keys(x)Object.getOwnPropertyNames(x)都无法获取它。这就造成了一种非私有的内部方法的效果。

Symbol.for(),Symbol.keyFor()

Symbol()函数返回的值都是独一无二的,如果我们希望使用相通的 Symbol 值,这时候Symbol.for就可以实现,它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。也就是说每次调用Symbol('yangchao')函数都生成新的值,如果调用 Symbol.for('yangchao'),会先检查 keyyangchao是否已经存在,如果不存在才会新建值,否则返回存在的值。

Symbol.for("yangchao") === Symbol.for("yangchao"); //true

Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key;

let s1 = Symbol.for("foo");
Symbol.keyFor(s1); // "foo"

注意!!! 注意!!!

  1. Symbol()函数写法没有登记机制,所以每次调用都会返回一个不同的值。

    let s2 = Symbol("foo");
    Symbol.keyFor(s2); // undefined
    

    上面代码中,因为s2属于未登记的 Symbol 值,所以返回 undefined

  2. Symbol.for为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值。

    iframe = document.createElement("iframe");
    iframe.src = String(window.location);
    document.body.appendChild(iframe);
    
    iframe.contentWindow.Symbol.for("foo") === Symbol.for("foo");
    // true
    

    上面代码中,iframe 窗口生成的 Symbol 值,可以在主页面得到。

实例:模块的 Singleton 模式

Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例。 对于 Node 来说,模块文件可以看成是一个类。怎么保证每次执行这个模块文件,返回的都是同一个实例呢?

很容易想到,可以把实例放到顶层对象global

// mod.js
function A() {
  this.foo = "hello";
}

if (!global._foo) {
  global._foo = new A();
}

module.exports = global._foo;

然后,加载上面的 mod.js。

const a = require("./mod.js");
console.log(a.foo);

上面代码中,变量 a 任何时候加载的都是 A 的同一个实例。

但是,这里有一个问题,全局变量 global._foo 是可写的,任何文件都可以修改。 为了防止这种情况出现,我们就可以使用 Symbol。

// mod.js
const FOO_KEY = Symbol("foo");

function A() {
  this.foo = "hello";
}

if (!global[FOO_KEY]) {
  global[FOO_KEY] = new A();
}

module.exports = global[FOO_KEY];

上面代码将导致其他脚本都无法引用FOO_KEY。但这样也有一个问题,就是如果多次执行这个脚本,每次得到的 FOO_KEY 都是不一样的。虽然 Node 会将脚本的执行结果缓存,一般情况下,不会多次执行同一个脚本,但是用户可以手动清除缓存,所以也不是绝对可靠。

内置的 Symbol 值

除了定义自己使用的 Symbol 值以外,ES6 还提供了 13 个内置的 Symbol 值,指向语言内部使用的方法,在大神阮一峰 Symbol 的讲解中,讲到了 11 个,但是我看官方文档,还多出两个,但是时间原因,这里就不做过多介绍了,可以阅读下一篇ES6-Symbol-3,或者阮一峰 SymbolSymbol 文档

总结

随着对 Symbol 的学习不断深入,发现在开发过程中很多位置都可以使用,比如常量定义,字典的定义,唯一 Key 值等位置都可以使用。今天看到 内置的 Symbol 值,下一步需要去仔细了解每个内置的值的属性和用法,继续加油,over~~~