Symbol简介
Symbol是ES6推出的JS第六大简单数据类型,其余五个分别是:
undefined null number string boolean
由于它是简单数据类型,所以不支持new
Symbol本身是一个函数,
通过Symbol()函数调用可以返回一个独一无二的值,Symbol()返回值仅用来表示唯一值,而不能用来和其他数据类型的值进行运算。
Symbol()常用来解决对象属性命令冲突问题
在JS开发中,我们经常需要给对象添加属性,但是当对象原本的属性很多时,或者对我们不可见时,我们就无法轻易给出要添加的属性的属性名了,因为可能会发送命名覆盖,比如重写call方法的例子
Function.prototype._call = function(thisArg, ...args){
thisArg.fn = this // 草率定义了'fn'为thisArg的属性
const result = thisArg.fn(...args)
delete thisArg.fn
return result
}
const obj = {
0: 0,
1: 1,
2: 2,
3: 3,
length: 4,
fn: 123
}
Array.prototype.forEach._call(obj, (item)=>{
console.log(item)
})
注意上面程序Function.prototype._call方法中定义 thisArg.fn = this 是一种非常草率的行为,因为我们不知道thisArg对象是否有一个叫fn属性名的属性,如果有的话,我们就会将thisArg原有的fn属性给覆盖了。
所以我们需要一个在thisArg对象中没有出现过的属性,在没有Symbo之前提出的方案是:
随机字符串+遍历对象属性(包括原型上)去重
这是一种繁琐,且费时的操作,这里就不演示了。
但是当ES6 Symbol提出后,解决了对象属性命名冲突的问题,因为Symbol()返回的每一个值都是独一无二,绝不会重复的。
我们利用Symbol对_call进行优化
Function.prototype._call = function(thisArg, ...args){
const fn = Symbol()
thisArg[fn] = this // 注意这里fn是一个变量,我们需要使用[]获取动态属性名,而不是使用.
const result = thisArg[fn](...args)
delete thisArg[fn]
return result
}
const obj = {
0: 0,
1: 1,
2: 2,
3: 3,
length: 4,
fn: 123
}
Array.prototype.forEach._call(obj, (item)=>{
console.log(item)
})
可以看到obj的原本属性没有受到任何影响。
Symbol定义对象私有属性
由于Symbol()返回的值是独一无二的,所以如果我们不对外提供Symbol()返回值,则外部无法访问到Symbol()定义的属性
const obj = {
[Symbol()]: '123'
}
console.log(obj)
给Symbol()打上标识
当我们用Symbol()返回值作为对象属性时,发现语义化不清晰,不知道Symbol()返回值到底是什么属性,是干什么的?Symbol函数支持传入一个标识字符串,来标识Symbol()属性的语义
const obj = {
[Symbol('name')] : 'zhangsan',
[Symbol('age')]: 28
}
打上标识后的Symbol()返回值语义更加清晰,同时保持着唯一性。
那么Symbol('name') 和 Symbol('name') 返回的值相同吗?
答案是:不相同
我们应该要理解 Symbol('name') 本质上还是Symbol(),只是为其返回值加了一个标识而已,所以即使标识相同,Symbol('name') 和 Symbol('name')返回值也不相同。
Symbol实现既保证对象属性唯一,又保证外部可访问
即定义一个不会发生命名冲突,但是又对外开放的对象属性
Symbol函数提供了一个for静态方法,该静态方法也能返回一个唯一值,但是和Symbol()不同的是,for方法可以指定一个key字符串,且JS内部会缓存 "key => symbol唯一值" 键值对到全局的symbol注册表,作用是当下次Symbol.for再次传入相同key时,会从全局的symbol注册表中找到key对应的symbol唯一值 作为返回值,即如下
通过这个特性可以实现 保证属性在对象内部唯一,又对外部开放
const obj = {
[Symbol.for('name')]: 123
}
console.log(obj[Symbol.for('name')])
另外对于Symbol还有一个静态方法keyFor,支持传入一个symbol唯一值,返回其key值
因为 全局的symbol注册表 中key必须是唯一的,且key对于的symbol值也是唯一的,所以既可以根据key找到symbol值,也可以根据symbol值找到key
另外还有一点 全局的symbol注册表 的全局性说明:
支持跨文件和跨域,这个我还没有验证。
Symbol内置值
Symbol上还有一些静态属性(内置值),这些Symbol内置值代表了内部语言行为,
比如我们要对一个对象进行for...of迭代时,for...of底层会去找被迭代对象的[Symbol.iterator]属性,如果被迭代对象有该属性,则执行函数调用 [Symbol.iterator]() 看是否可以返回一个 迭代器对象,若是,则可以进行for...of迭代,如不是则无法进行for...of迭代。
我们可以发现Symbol.iterator就是一个Symbol内置值,iterator作为Symbol的静态属性,而且该Symbol内置值和for...of的底层运行逻辑存在关系
const obj = {
0: 0,
1: 1,
2: 2,
3: 3,
length: 4,
[Symbol.iterator](){
let cursor = 0
let length = this.length
let that = this
return {
next(){
return {
value: that[cursor++],
done: cursor > length
}
}
}
}
}
for(let i of obj) {
console.log(i)
}
再比如当我们使用instanceof检测类型是否在对象的原型链上时,会先去找类型身上的[Symbol.hasInstance]属性,找到则执行函数调用 [Symbol.hasInstance](对象) ,该函数的返回值就是instanceof的结果
// function Person() {}
// Person[Symbol.hasInstance] = function(instance) {
// console.log('进来了')
// while (true) {
// if (Object.getPrototypeOf(instance) === Person.prototype) {
// return true
// } else {
// if (Object.getPrototypeOf(instance)) {
// instance = Object.getPrototypeOf(instance)
// } else {
// return false
// }
// }
// }
// }
class Person {
static[Symbol.hasInstance](instance) {
console.log('进来了')
while (true) {
if (Object.getPrototypeOf(instance) === Person.prototype) {
return true
} else {
if (Object.getPrototypeOf(instance)) {
instance = Object.getPrototypeOf(instance)
} else {
return false
}
}
}
}
}
const p = new Person()
console.log(({}) instanceof Person)
console.log(p instanceof Person)
另外,好像当Symbol内置值是类的静态属性时,只有ES6 class支持,而es5构造函数不支持
其他内置值使用,请看MDNSymbol - JavaScript | MDN (mozilla.org)
遍历对象的Symbol属性
当我们给对象添加一个Symbol类型属性时
let obj = {
a: 1,
b: 2,
[Symbol()]: 3,
[Symbol('c')]: 4,
[Symbol.for('d')]: 5
}
Object.getOwnPropertyDescriptors(obj)
可以发现Symbol类型属性都是可遍历,可删除,可修改的
但是当我们for...in遍历时,却发现只能遍历出非Symbol类型的属性,即使Symbol类型属性是可遍历的
另外 Object.getOwnPropertyNames也不支持返回对象Symbol类型属性
以及Object.keys也不支持返回对象Symbol类型属性
另外 JSON.stringif也不支持序列化对象的Symbol类型属性
目前Object.getOwnPropertySymbols支持返回对象的Symbol属性
以及反射支持支持返回对象的Symbol属性