ES6 引入了一种新的原始数据类型Symbol
,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined
、null
、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。Symbol 值通过Symbol
函数生成。
let s = Symbol(); typeof s // "symbol"
在symbol类型出来后,对象拥有了两种数据类型,一个是字符串,另一个就是symbol数据类型。属性名是symbol类型的可以保证不会与其他属性造成冲突
symbol函数可以接受一个字符串作为变量。表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
let s1 = Symbol('foo'); let s2 = Symbol('bar'); s1 // Symbol(foo) s2 // Symbol(bar) s1.toString() // "Symbol(foo)" s2.toString() // "Symbol(bar)"
这个参数仅仅只是作为我们对symbol这数据的描述,用来区分不同的symbol数据,所以如果我们对Symbol函数传入了相同的参数,即使他们的描述相等,但是Symbol的返回值是不同的。
let s1 = Symbol('foo'); let s2 = Symbol('foo'); s1 === s2 // false
Symbol 值不能与其他类型的值进行运算,会报错。但是,Symbol 值可以显式转为字符串,也可以转为布尔值,但是不能转为数值。即Symbol是可以调用toString方法和作为参数传入Boolean方法的
但是无法作为参数传入Number函数转为数值的。
我们在创建Symbol的时候会给他添加一个描述,ES2019提供了一个实例方法,会直接返回Symbol的描述
const sym = Symbol('foo'); sym.description // "foo"
因为Symbol作为独一无二的值,他往往会被用在对象的属性中,防止对象的属性被覆盖或者改写。值得注意的是,在Symbol出现之前,我们在对象中所指定的属性名都是以字符串的形式存在,也仅仅只能以字符串形式存在。ES6出现了新的数据结构map,它与对象相似,但是却可以以其他的数据类型作为属性名存在。同样的ES6出现Symbol类型之后,传统的对象属性名称不仅可以用字符串表示,也可以用Symbol表示。
但是请注意,Symbol 值作为对象属性名时,不能用点运算符。对象读取属性常见的方法有两种
obj.key obj[key]
使用Symbol类型作为对象属性的读取
let mySymbol = Symbol(); // 第一种写法 let a = {}; a[mySymbol] = 'Hello!';
// 第二种写法 let a = { [mySymbol]: 'Hello!' };
// 第三种写法 let a = {}; Object.defineProperty(a, mySymbol, { value: 'Hello!' }); // 以上写法都得到同样结果 a[mySymbol] // "Hello!"
点运算符后面总是字符串,所以不会读取mySymbol
作为标识名所指代的那个值,导致a
的属性名实际上是一个字符串,而不是一个 Symbol 值。代码如下
const mySymbol = Symbol(); const a = {}; a.mySymbol = 'Hello!'; a[mySymbol] // undefined a['mySymbol'] // "Hello!"
属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
有一个Object.getOwnPropertySymbols()
方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用Symbol 作属性名的 。
const obj = {}; let a = Symbol('a'); let b = Symbol('b'); obj[a] = 'Hello'; obj[b] = 'World'; const objectSymbols = Object.getOwnPropertySymbols(obj); objectSymbols // [Symbol(a), Symbol(b)]
另一个新的 API,Reflect.ownKeys()
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = { [Symbol('my_key')]: 1, enum: 2, nonEnum: 3 }; Reflect.ownKeys(obj) // ["enum", "nonEnum", Symbol(my_key)]
因为symbol这一个不能被轻易遍历到的特性,我们可以将一些非私有但是仅仅用在对象内部的属性用symbol保存在对象中
使用同一个symbol
有时,我们希望重新使用同一个 Symbol 值,Symbol.for()
方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局
let s1 = Symbol.for('foo'); let s2 = Symbol.for('foo'); s1 === s2 // true
s1
和s2
都是 Symbol 值,但是它们都是由同样参数的Symbol.for
方法生成的,所以实际上是同一个值。
Symbol.for()
与Symbol()
这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境*搜索,后者不会。就像diff算法一样,Symbol.for()调用返回新的symbol类型的时候会去寻找是否有相同key(也就是symbol的描述)的symbol存在,若不存在则创建新的,若存在则不创建新的symbol。而Symbo()则是调用即创建新的数据类型。我们将symbol.for()这一特性称为symbol的登记机制。
Symbol.keyFor()
方法返回一个已登记的 Symbol 类型值的key
。
let s1 = Symbol.for("foo"); Symbol.keyFor(s1) // "foo" let s2 = Symbol("foo"); Symbol.keyFor(s2) // undefined
注意,Symbol.for()
为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。
实例:模块的 Singleton 模式(参考阮一峰教程)
Singleton 模式指的是调用一个类,任何时候返回的都是同一个实例。
对于 Node 来说,模块文件可以看成是一个类。怎么保证每次执行这个模块文件,返回的都是同一个实例呢?
很容易想到,可以把实例放到顶层对象global
。
以下是mod.js的代码,以供暴露实例以供其他模块使用
// 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);//hello
上面代码中,变量a
任何时候加载的都是A
的同一个实例。
但是,这里有一个问题,全局变量global._foo
是可写的,任何文件都可以修改。
global._foo = { foo: 'world' }; const a = require('./mod.js'); console.log(a.foo);
上面的代码,会使得加载mod.js
的脚本都失真。
为了防止这种情况出现,我们就可以使用 Symbol.for。
// mod.js const FOO_KEY = Symbol.for('foo'); function A() { this.foo = 'hello'; } if (!global[FOO_KEY]) { global[FOO_KEY] = new A(); } module.exports = global[FOO_KEY];
上面代码中,可以保证global[FOO_KEY]
不会被无意间覆盖,但还是可以被改写。
// mod.js const FOO_KEY = Symbol('foo'); // 后面代码相同 ……
global[Symbol.for('foo')] = { foo: 'world' }; const a = require('./mod.js');
如果键名使用Symbol
方法生成,那么外部将无法引用这个值,当然也就无法改写。
// mod.js const FOO_KEY = Symbol('foo'); // 后面代码相同 ……
上面代码将导致其他脚本都无法引用FOO_KEY
。但这样也有一个问题,就是如果多次执行这个脚本,每次得到的FOO_KEY
都是不一样的。虽然 Node 会将脚本的执行结果缓存,一般情况下,不会多次执行同一个脚本,但是用户可以手动清除缓存,所以也不是绝对可靠。
第一次认真的看一篇文档,便将重要的东西挑出来简述,symbol是以前都没接触过的类型,也是没使用过的。我感觉symbol的出现,让JS好像变得越来越严谨希望有朝一日JS可以变得越来越强大。
宣传下自己的网站mishi-blog.com
尚在襁褓中的一个网站。