ECMAscript新特性 - Symbol 一种全新的原始数据类型

152 阅读4分钟

在 ES2015 之前对象的属性名都是字符串,而字符串是有可能重复的。如果重复的话就会产生冲突,解决这类冲突常用的方式就是约定。

// shared.js
const catch = {}
// a.js
catch['a_foo'] = Math.random()
// b.js
catch['b_foo'] = '123'

但是约定的方式只是规避了问题,并不是彻底解决了这个问题。如果在这样一个过程当中有人不遵守约定,这个问题仍然会存在。ES2015 为了解决这种类型的问题,提供了一种全新的原始数据类型叫做 Symbol ,它的作用就是表示一个独一无二的值,通过 Symbol 函数创建的每一个值都是唯一的永远不会重复。为了便于调试 Symbol 函数允许传递一个字符串作为描述文本,这样的话对于多次使用 Symbol 的情况就可以在控制台区分出到底是那个对应的 Symbol 。

const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // Symbol
console.log(Synbol() === Symbol()) // false
console.log(Symbol('foo')) // Symbol(foo)

从 ES2015 开始对象就可以直接去使用 Symbol 类型的值作为属性名,也就是说对象的属性名可以有两种类型的值分别是 String 和 Symbol。

const obj = {};
obj[Symbol()] = 123;
obj[Symbol()] = 456;
console.log(obj) // {[Symbol()]:123, [Symbol()]:456}

另外 Symbol 除了可以用来去避免对象属性名重复产生的问题,还可以借助这种类型的特点去模拟实现对象的私有成员。

// a.js
const name = Symbol()
const person = {
    [name]: 'leo',
    say () { // 在对象的内部可以使用创建属性时的 Symbol 去拿到对应的属性成员
        console.log(this[name])
    }
}
// b.js
// 在外部文件当中因为我们没有办法再去创建一个完全相同的 Symbol 
// 所以说就不能够直接去访问到这个成员 
// 只能去调用这个对象当中普通名称的成员这样就实现了所谓的私有成员
person.say()

Symbol 最主要的作用就是为对象添加独一无二的属性名,截止到 ECMAscript2019 ECMAscript 当中一共定义了6中原始数据类型加上Object数据类型,一共7种数据类型。在未来还会新增一个叫做 BigInt 的原始数据类型用于去存放更长的数字,只不过这个类型处在 stage-4 阶段预计在下一个版本就能正式被标准化,到时候就8种数据类型了。

Symbol 在使用上还有一些让我们注意的地方,首先是它的唯一性每次通过 Symbol 函数创建的值一定是一个唯一的值,不管我们传入的描述文本是不是相同的,每次去调用 Symbol 函数它得到的结果都是全新的一个值。如果我们需要在全局去复用一个相同的 Symbol 值,可以使用全局变量的方式去实现,或者是去使用 Symbol 类型提供的一个静态方法去实现。

Symbol() === Symbol() // false
Symbol('foo') === Sybol('foo') // false
const s1 = Symbol.for('foo'); 
const s2 = Symbol.for('foo'); 
// 相同的字符串就一定会返回相同的 Symbol 类型的值// 这个方法它内部维护了一个全局的注册表,为字符串和 Symbol 值提供了一个一一对应的关系
// 需要注意的是,这个方法内部维护的是字符串和 Symbol 之间的对应关系,也就是说如果传入的不是字符串
// 这个方法内部,会把它自动转化成字符串,
// 这样就会导致传入布尔值的true 和字符串的 true 结果拿到的都是一样的
console.log(s1 === s2) // true

在 Symbol 类型当中还提供了很多内置的 Symbol 常量用来去作为内部方法的标示,这些标识符可以让自定义对象去实现一些 JS 当中内置的接口。如果想要自定义 toString 标签就可以在对象当中去添加一个特定成员来标识 ,考虑到如果使用字符串添加这种标识符就可能跟内部的一些成员产生重复。所以说 ECMSscript 要求使用 Symbol 值去实现这样一个接口 ,这里的 toStringTag 就是内置的一个 Symbol 常量 。

const obj = {};
const xobj = {
    [Symbol.toStringTag]: 'XObject'
};
console.log(obj.toString()) // [object object] 对象的 toString 标签
console.log(xobj.toString()) // [object XObject]

这种 Symbol 我们在后面为对象去实现迭代器时会经常用到。最后使用 Symbol 值做为对象的属性名,这个属性通过传统的 for in 循环是无法拿到的; Object.keys 方法也是获取不到的;通过 JSON.stringfy(obj) Sybole 属性也会被忽略掉。 总之这些特性都使得 Symbol 类型的属性它特别适合作为对象的私有属性,当然想要获取这种类型的属性名也不是完全没有办法,可以使用 Object 对象里面的 getOwnPropertySymbols 方法 ,它的作用类型于 Object.keys 方法所不同的是 Object.keys 只能获取字符串类型的属性名,getOwnPropertySymbols 方法只能获取 Symbol 类型的属性名。

const obj = {
    [Symbol()]: 'symbol value',
    foo: 'normal value',
}
for (let key in obj) {
    console.log(key)
} // foo

Object.keys(obj) // ['foo']
JSON.stringfy(obj) // {"foo":"normal value"}
Object.getOwnPropertySymbols(obj) // [ Symbol() ]