ES2015 Symbol数据类型
对象的属性名同名的问题
在 ECMAScript2015 之前对象的属性名都是字符串类型,而字符串是有可能会重复的,那如果重复的话就会产生冲突。比如创建一个用来数据缓存的对象,约定这个对象是全局共享的,然后通过注释的方式模拟不同的 JavaScript 文件使用这个数据缓存对象的情况。
比如在 shared.js
文件中创建这个数据缓存对象,如下代码所示:
const cache = {}
然后在 a.js
文件当中向数据缓存对象添加缓存数据,如下代码所示:
cache['foo'] = Math.random()
这里主要关注对象的属性名,也就是键的问题。然后在 b.js
文件当中再次向数据缓存对象添加缓存数据,如下代码所示:
cache['foo'] = '123'
这里很可能不知道这个缓存数据对象已经存在相同名称的键,也去使用相同名称作为键存放另外一个数据,这样就会产生冲突。
现如今会大量使用第三方模块,很多时候都会需要去扩展第三方模块中提供的一些对象,而此时是不知道这个对象当中是否会存在某一个指定的键,如果冒冒然地去扩展就有可能会产生冲突的问题。
以前解决这种问题的方式就是人为约定,比如这里约定在 a.js
文件放入的缓存键都以 a_
开头,而 b.js
文件当中都以 b_
开头。如下代码所示:
/* a.js */
cache['a_foo'] = Math.random()
/* b.js */
cache['b_foo'] = '123'
但是约定的方式只是规避了问题,并不是彻底地解决了这个问题。在这个过程当中如果有人不遵守这个约定的话,这个问题仍然存在。
Symbol 数据类型
ECMAScript2015 为了解决这样的问题新增了一个全新的数据类型叫 Symbol
,译为符号,它的作用就是表示一个独一无二的值。
可以通过 Symbol()
函数创建一个 Symbol
类型的数据,如下代码所示:
const s = Symbol()
console.log(s)
上述代码的运行结果如下:
Symbol()
而且使用 typeof
运算符判断这个数据类型的结果就是 Symbol
,这也就表示 Symbol
是一个全新的类型。如下代码所示:
console.log(typeof s)
Symbol 类型的最大特点就是独一无二,也就是说,通过 Symbol()
函数创建的每一个值都是唯一的。如下代码所示:
console.log(Symbol() === Symbol())
上述代码的运行结果如下:
false
考虑到在开发过程中的调试,Symbol()
函数允许传递一个字符串作为这个值的描述文本。这样的话,对于多次使用 Symbol
的情况就可以在控制台区分当前使用的到底是哪一个 Symbol
数据。如下代码所示:
console.log(Symbol('foo'))
console.log(Symbol('bar'))
console.log(Symbol('baz'))
上述代码的运行结果如下:
Symbol(foo)
Symbol(bar)
Symbol(baz)
而且从 ECMAScript2015 开始,对象就可以直接使用 Symbol
类型的值作为属性名。也就是说,现在对象的属性名可以是两种类型,分别是 String
和 Symbol
。如下代码所示:
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
上述代码的运行结果如下:
{ [Symbol()]: '123', [Symbol()]: '456' }
从打印的结果可以看到,因为 Symbol
值都是独一无二的,所以这里就不用担心可能会产生冲突的问题了。这里也可以使用计算属性名的方式直接在对象字面量当中使用 Symbol
作为属性名。如下代码所示:
const obj = {
[Symbol()]: 123
}
模拟实现对象的私有成员
另外,Symbol
除了可以避免对象的属性名重复产生的问题,还可以借助这种类型的特点模拟实现对象的私有成员。如下代码所示:
/* a.js */
const name = Symbol()
const person = {
[name]: '前端课湛',
say() {
console.log(this[name])
}
}
/* b.js */
console.log(person[Symbol()])
person.say()
如果在 a.js
文件中创建一个对象,使用 Symbol
作为其中一个属性名的话,那在外部(b.js
文件)是无法访问到以 Symbol 作为属性名的属性或方法的。
上述代码的运行结果如下:
undefined
前端课湛
Symbol
类型最主要的作用就是为对象添加独一无二的属性名。
Symbol 的唯一性
Symbol
在使用上还有一些值得注意的地方。首先是 Symbol 的唯一性,每次通过 Symbol 创建的值一定是唯一的值,不管传入的文本描述是不是相同的。如下代码所示:
console.log(
Symbol() === Symbol(),
Symbol('foo') === Symbol('foo')
)
上述代码的运行结果如下:
false false
如果需要在全局复用一个相同的 Symbol
值,可以使用全局变量的方式去实现,或者使用 Symbol
提供的一个 for()
静态方法去实现。如下代码所示:
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2)
上述代码的运行结果如下:
true
for()
方法内部维护了一个全局的注册表,为字符串和 Symbol
值提供了一个一一对应的关系。需要注意的是,for()
方法内部维护的是字符串和 Symbol
之间的关系。也就是说,如果传入的不是字符串,for()
方法内部会自动把它转换成字符串。如下代码所示:
console.log(Symbol.for(true) === Symbol.for('true'))
上述代码的运行结果如下:
true
从上述打印结果可以看到,for()
方法内部将 Boolean
类型的 true
转换成了 String
类型的 true
,所以两者比较的结果为 true。
Symbol
的这一特点尤其需要注意。
Symbol 提供的常量
而且,在 Symbol
类型还提供了很多内置的 Symbol
常量,用来作为内部方法的标识。这些标识符可以让自定义对象去实现一些 JavaScript 当中内置的接口。如下代码所示:
const obj = {}
console.log(obj.toString())
上述代码的运行结果如下:
[object Object]
上述打印结果被称为 obj
对象的 toString
标签。如果想要自定义这个对象的 toString
标签,就可以在这个对象当中添加一个特殊的成员来进行标识。如下代码所示:
const obj = {
[Symbol.toStringTag]: 'XObject'
}
console.log(obj.toString())
上述代码的运行结果如下:
[object XObject]
Symbol 值作为对象属性名的特性
最后,使用 Symbol
值作为对象的属性名,通过 for...in
循环是无法获取的。如下代码所示:
const obj = {
[Symbol()]: 'Symbol value',
foo: 'normal value'
}
for (let key in obj) {
console.log(key)
}
上述代码的运行结果如下:
foo
而且,通过 Object.keys()
方法同样无法获取使用 Symbol
值作为对象的属性名。再有就是如果通过 JSON.stringify()
序列化对象为一个 JSON
字符串的话,使用 Symbol
值作为对象的属性名也会被忽略掉。
总之,这些特性都使得 Symbol
类型的属性特别适合作为对象的私有属性。当然,想要获取这种类型的属性名也不是没有办法的,可以使用 Object.getOwnPropertySymbols()
方法。如下代码所示:
console.log(Object.getOwnPropertySymbols(obj))
上述代码的运行结果如下:
[ Symbol() ]
Object.getOwnPropertySymbols()
方法的作用类似于 Object.keys()
方法,所不同的是 Object.keys()
方法只能获取字符串作为属性名的属性,而 Object.getOwnPropertySymbols()
方法获取的都是 Symbol
作为属性名的属性。
本文地址:https://blog.csdn.net/kingj_fullstack/article/details/107544042
上一篇: element-ui的message组件实现机制解析
下一篇: wyt1210笔试、面试