背景
ES6 引入了一种新的基本数据类型 Symbol,表示独一无二的值。而且 Symbol 的内容比较多,作为个人也学习了两天左右,所以才有了这篇,对于 Symbol 更多的介绍,上一篇基础介绍可以查看ES6-Symbol。如果想一次看详细内容,可以阅读阮一峰大神关于 Symbol的讲解,本文很多都是参照和 copy,然后加上个人实验,想详细了解,可以查看大神的讲解。
属性名的遍历
Symbol 作为属性名,该属性不会出现在 for...in
、for...of
循环中,也不会被 Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。但是,它也不是私有属性,有一个 Object.getOwnPropertySymbols
方法,可以获取指定对象的所有 Symbol 属性名。
Object.getOwnPropertySymbols
方法返回一个数组,成员是当前对象的所有用作属性名的 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)]
上面代码中,对象 x
的 size
属性是一个 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"
注意!!! 注意!!!
-
Symbol()函数写法没有登记机制,所以每次调用都会返回一个不同的值。
let s2 = Symbol("foo"); Symbol.keyFor(s2); // undefined
上面代码中,因为
s2
属于未登记的 Symbol 值,所以返回undefined
。 -
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,或者阮一峰 Symbol,Symbol 文档。
总结
随着对 Symbol 的学习不断深入,发现在开发过程中很多位置都可以使用,比如常量定义,字典的定义,唯一 Key 值等位置都可以使用。今天看到 内置的 Symbol 值,下一步需要去仔细了解每个内置的值的属性和用法,继续加油,over~~~