阅读 313

ES6 Symbol之浅入解读😂

一、介绍

Symbol是ES6新增的数据类型,是一种基础数据类型,MDN 中的对Symbol类型的描述为:数据类型 “symbol” 是一种原始数据类型,该类型的性质在于这个类型的值可以用来创建匿名的对象属性。该数据类型通常被用作一个对象属性的键值——当你想让它是私有的时候。


symbol 数据类型具有非常明确的目的,并且因为其功能性单一的优点而突出;一个 symbol 实例可以被赋值到一个左值变量,还可以通过标识符检查类型,这就是它的全部特性。

这些描述几乎完全说明了这个新来的数据类型的用途:作为一个对象或一个Map的键值,他可以保证你的对象或Map的键值不重复(这个在某些场景下真的非常有用)。 这个数据类型因为是ES6新增的,所以不存在polyfill。

二、用法

1. 对象

1.1 普通用法

能够用来创建Symbol的是一个像类的函数 Symbol(),用来创建 symbol 数据类型实例。注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。【此处引用阮一峰老师在ES6教程里面的话】MDN上的解释是:一个具有数据类型 “symbol” 的值可以被称为 “符号类型值”。在 JavaScript 运行时环境中,一个符号类型值可以通过调用函数 Symbol() 创建,这个函数动态地生成了一个匿名,唯一的值。Symbol类型唯一合理的用法是用变量存储 symbol的值,然后使用存储的值创建对象属性。 最简单的作为对象的key的写法如下:

var  privateKey  = Symbol();
var obj = {
    [privateKey] : 'hero'
}
//访问时
obj[privateKey]  //hero
复制代码

一般在键值对对象中我们访问某个属性的时候常用.符号来取到对应的值,但是用.符号去取值时,.后面一定是一个字符串,因为ES6之前对象的key必须是字符串,所以当访问以Symbol实例为key时需要使用[]来包裹起来 (ES6同时增加了两种数据结构Set和Map,Map在某种意义上来讲,更加适合用来保存键值对的形式的数据,可以参照Java的Map的数据结构)。

var identity = Symbol()
var obj = {
    name : 'john',
    [identity] : 'hero'
}
obj.name   //'john'
obj[name]  //'john'
obj.identity   //undefined
//当我们使用.来访问时,因为不存在这个key,所以就会返回undefined,也符合上方所写的
obj[identity]  //'hero'
复制代码
1.2 遍历含有Symbol的对象

但是我们在遍历某个对象时,使用for in或for of方法时,Symbol为key或value时是不会出现在遍历结果里的;同样也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。但是伴随着Symbol一起出现的有一个方法可以取到它的值:Object.getOwnPropertySymbols,可以获取指定对象的所有 Symbol 属性名。 Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的 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可以用来将一个对象里面所有的键值全部反映出来,包含普通键和Symbol键:Reflect.ownKeys。【参考自阮一峰的ES6教程】

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

Reflect.ownKeys(obj)
//  ["enum", "nonEnum", Symbol(my_key)]
复制代码
1.3 复用Symbol

Symbol虽然旨在给我们提供一个永远不会重复的特殊值,但是也确实会存在需要相同Symbol的场景,API永远比我们想得多,Symbol有一个方法:Symbol.for,它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true
复制代码

这个API的工作原理大概流程是这样的:Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。【参考自阮一峰的ES6教程】 同样的,Symbol.keyFor这个API可以返回一个已登记的 Symbol 类型值的key。

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

let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
//因为s2不是以Symbol.for()创建的,所以使用Symbol.keyFor()无法得到其值
复制代码

2. 通用方法

当然,使用的时候为了更容易区分,一般而言我们会向Symbol()里面传递一个参数为了区分不同的Symbol,例如Symbol('name')这样,这样更有利于开发时进行调试

let symbol1 = Symbol('name');
let symbol12 = Symbol('age');

symbol1 // Symbol(name)
symbol2 // Symbol(age)
//上方的只是返回值而已,即使返回值相同,也不代表这两个Symbol相同!

symbol1.toString() // "Symbol(name)"
symbol2.toString() // "Symbol(age)"

//另外一个就是Symbol 值不能与其他类型的值进行运算,会报错。
Symbol('hero') + ' hello'  //TypeError: can't convert symbol to string
//如果真的要处理,我们只能先显式的将Symbol转化为字符串才能和其他数字或字符串相加
Symbol('hero').toString() + ' hello'     //"Symbol(hero) hello"
//Symbol可以转化为布尔值
Boolean(Symbol('hero'))     //true
//Symbol无法通过某种方式直接转化为数字
Number(Symbol('hero')) // TypeError
复制代码

三、结语

Symbol的出现目的很单一,就是作为对象的键值,常用的一些方法也非常好理解,关键是这个可以非常有效的消除magic string和magic number啊喂😂。

关注下面的标签,发现更多相似文章
评论