2021.04.28(属性名的遍历、Symbol.for(),Symbol.keyFor()、实例:模块的 Singleton 模式)

属性名的遍历

Symbol 作为属性名,遍历对象的时候,该属性不会出现在 for...in  、 for...of  循环中,也不
会被 Object.keys()  、 Object.getOwnPropertyNames()  、 JSON.stringify()  返回。

但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols()  方法,可以获取指定对象的
所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。

1.  const obj = {};
2.  let a = Symbol('a');
3.  let b = Symbol('b');
4.
5.  obj[a] = 'Hello';
6.  obj[b] = 'World';
7.
8.  const objectSymbols = Object.getOwnPropertySymbols(obj);
9.
10.  objectSymbols
11.  // [Symbol(a), Symbol(b)]

上面代码是 Object.getOwnPropertySymbols()  方法的示例,可以获取所有 Symbol 属性名。

下面是另一个例子, Object.getOwnPropertySymbols()  方法与 for...in  循
环、 Object.getOwnPropertyNames  方法进行对比的例子。

1.  const obj = {};
2.  const foo = Symbol('foo');
3.
4.  obj[foo] = 'bar';
5.
6.  for (let i in obj) {
7.  console.log(i); // 无输出
8.  }
9.
10.  Object.getOwnPropertyNames(obj) // []
11.  Object.getOwnPropertySymbols(obj) // [Symbol(foo)]

上面代码中,使用 for...in  循环和 Object.getOwnPropertyNames()  方法都得不到 Symbol 键
名,需要使用 Object.getOwnPropertySymbols()  方法。

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

1.  let obj = {
2.  [Symbol('my_key')]: 1,
3.  enum: 2,
4.  nonEnum: 3
5.  };
6.
7.  Reflect.ownKeys(obj)
8.  // ["enum", "nonEnum", Symbol(my_key)]

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

Symbol.for(),Symbol.keyFor()

有时,我们希望重新使用同一个 Symbol 值, Symbol.for()  方法可以做到这一点。它接受一个字
符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol
值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。

1.  let s1 = Symbol.for('foo');
2.  let s2 = Symbol.for('foo');
3.
4.  s1 === s2 // true

上面代码中, s1  和 s2  都是 Symbol 值,但是它们都是由同样参数的 Symbol.for  方法生成
的,所以实际上是同一个值。

Symbol.for()  与 Symbol()  这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记
在全局环境中供搜索,后者不会。 Symbol.for()  不会每次调用就返回一个新的 Symbol 类型的
值,而是会先检查给定的 key  是否已经存在,如果不存在才会新建一个值。比如,如果你调
用 Symbol.for("cat")  30 次,每次都会返回同一个 Symbol 值,但是调用 Symbol("cat")  30
次,会返回 30 个不同的 Symbol 值。

1.  Symbol.for("bar") === Symbol.for("bar")
2.  // true
3.
4.  Symbol("bar") === Symbol("bar")
5.  // false

上面代码中,由于 Symbol()  写法没有登记机制,所以每次调用都会返回一个不同的值。

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

1.  let s1 = Symbol.for("foo");
2.  Symbol.keyFor(s1) // "foo"
3.
4.  let s2 = Symbol("foo");
5.  Symbol.keyFor(s2) // undefined

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

注意, Symbol.for()  为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。

1.  function foo() {
2.  return Symbol.for('bar');
3.  }
4.
5.  const x = foo();
6.  const y = Symbol.for('bar');
7.  console.log(x === y); // true

上面代码中, Symbol.for('bar')  是函数内部运行的,但是生成的 Symbol 值是登记在全局环境
的。所以,第二次运行 Symbol.for('bar')  可以取到这个 Symbol 值。

Symbol.for()  的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同
一个值。

1.  iframe = document.createElement('iframe');
2.  iframe.src = String(window.location);
3.  document.body.appendChild(iframe);
4.
5.  iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
6.  // true

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

实例:模块的 Singleton 模式

Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例。

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

1.  // mod.js
2.  function A() {
3.  this.foo = 'hello';
4.  }
5.
6.  if (!global._foo) {
7.  global._foo = new A();
8.  }
9.
10.  module.exports = global._foo;

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

1.  const a = require('./mod.js');
2.  console.log(a.foo);

上面代码中,变量 a  任何时候加载的都是 A  的同一个实例。
但是,这里有一个问题,全局变量 global._foo  是可写的,任何文件都可以修改。

1.  global._foo = { foo: 'world' };
2.
3.  const a = require('./mod.js');
4.  console.log(a.foo);

上面的代码,会使得加载 mod.js  的脚本都失真。
为了防止这种情况出现,我们就可以使用 Symbol。

1.  // mod.js
2.  const FOO_KEY = Symbol.for('foo');
3.
4.  function A() {
5.  this.foo = 'hello';
6.  }
7.
8.  if (!global[FOO_KEY]) {
9.  global[FOO_KEY] = new A();
10.  }
11.
12.  module.exports = global[FOO_KEY];

上面代码中,可以保证 global[FOO_KEY]  不会被无意间覆盖,但还是可以被改写。

1.  global[Symbol.for('foo')] = { foo: 'world' };
2.
3.  const a = require('./mod.js');

如果键名使用 Symbol  方法生成,那么外部将无法引用这个值,当然也就无法改写。

1.  // mod.js
2.  const FOO_KEY = Symbol('foo');
3.
4.  // 后面代码相同 ……

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

 

2021-04-29  17:26:00

posted @ 2021-04-29 17:27  铁打的代码流水的bug  阅读(278)  评论(0编辑  收藏  举报