先简单说说什么是Symbol
- Symbol是ES6新增的基础数据类型,它的特点就是独一无二的,如同UUID一样;
- Symbol是函数,通过调用Symbol函数来创建Symbol数据;
- Symbol还是内置对象,提供一系列函数well-known Symbol方法来改变JS语言的内部行为;
Symbol的特性与使用
示例:创建Symbol数据
- Symbol没有字面量的创建方式,也不能以
new Symbol()
构造函数的方式创建,只能通过调用Symbol([description])
函数,或者Symbol.for()
来创建。
// Symbol不允许以new关键的构造函数方式调用
new Symbol()
// Uncaught TypeError: Symbol is not a constructor
// 创建无描述的Symbol数据
let symbol1 = Symbol()
// 创建带描述的Symbol数据
let localSymbol Symbol('desc1')
// 在全局环境创建Symbol数据
let globalSymbol = Symbol.for('desc1')
// 全局注册表的Symbol和Symbol函数创建的Symbol是不一样的
console.log(localSymbol === globalSymbol)
// 输出:false
复制代码
特性:Symbol总是唯一的
- 数据总是独一无二的,不仅在函数、模块甚至在window顶层作用域中都是唯一的
// 调用Symbol函数,在当前环境中(函数作用域/模块作用域)创建唯一的symbol数据,虽然toString输出的结果看起来一样,但两者是不相等的
let f1 = Symbol('flag')
let f2 = Symbol('flag')
console.log(f1,f2)
// 输出:Symbol(flag) Symbol(flag)
console.log(f1 === f2)
// 输出:false
console.log(f1 === 'flag')
// 输出false
// 调用Symbol.for(key)方法,在是全局环境中创建Symbol数据。
// 当调用方法是,会根据key名来进行幂等操作,发现不存在则创建,如果已存在则返回
let lock = Symbol.for('flag')
let lockFlag = Symbol.for('flag')
console.log(lock === lockFlag)
// 输出:true
// Symbol()函数和Symbol.for()创建的Symbol数据不一样
console.log(f1 === lock)
// 输出:false
// 在全局环境下如果不想创建,只想查找,可通过Symbol.keyFor()方法
console.log(Symbol.keyFor('flag')) // flag
console.log(Symbol.keyFor('test')) // undefined但不会创建
复制代码
示例:使用Symbol来定义常量
- 既然Symbol的特性是唯一标志,我们可以用Symbol来做常量。
- 以前我们定义常量是这样婶的:
const FRUIT = {
APPLE: 'APPLE',
BANANA: 'BANANA',
STRAWBERRY: 'STRAWBERRY'
}
// 调用的时候,其实我们并不关心value是什么,只看key
console.log(FRUIT.APPLE)
//但万一有个傻子,加了一个菠萝,但值写成了苹果,判断就会炸裂
const FRUIT = {
APPLE: 'APPLE',
BANANA: 'BANANA',
STRAWBERRY: 'STRAWBERRY',
PINEAPPLE: 'APPLE' // 新增
}
// 而通过Symbol定义的话,就会避免这样的问题
const FRUIT = {
APPLE: Symbol(),
BANANA: Symbol(),
STRAWBERRY: Symbol()
}
function translate(FRUIT_TYPE){
switch (FRUIT_TYPE) {
case FRUIT.APPLE:
console.log('苹果')
break;
case FRUIT.BANANA:
console.log('香蕉')
break;
case FRUIT.STRAWBERRY:
console.log('草莓')
break;
default:
console.log('未匹配')
break;
}
}
translate(FRUIT.APPLE)
// 输出:苹果
复制代码
示例:使用Symbol来定义人名
- 比如一个班级里面,想通过人名来作为唯一标识,但人名又没办法避免重复,通过Symbol来实现
const grade = {
[Symbol('Lily')]: {
address: 'shenzhen',
tel: '186******78'
},
[Symbol('Annie')]: {
address: 'guangzhou',
tel: '183******12'
},
// 允许重复的名称
[Symbol('Lily')]: {
address: 'beijing',
tel: '172******10'
},
}
复制代码
特性:Symbol的类型判断和类型转换
- 和String类型一样,Symbol类型可以通过
typeof
操作符进行类型判断
let symbol = Symbol()
console.log(typeof Symbol)
// 输出:symbol
复制代码
- 但和String类型不一样的是,Symbol不会进行隐式的自动类型转换,所以不能直接进行字符串拼接运算和算术运算。但可以人为的进行显式类型转换,比如转成String、Boolean、Number、Object
let symbolUUID = Symbol('uuid')
// 不能直接进行字符串拼接操作
console.log(symbolUUID + '测试')
// TypeError: Cannot convert a Symbol value to a string
// 也不能直接进行算数操作
console.log(symbolUUID + 1)
// TypeError: Cannot convert a Symbol value to a number
// 但可以进行三目运算的boolean判断操作
console.log(symbolUUID ? '真' : '假')
// 输出:真
console.log(String(symbolUUID) + '测试')
// 输出:Symbol(uuid)测试
// 等价于symbolUUID.toString()
复制代码
特性:Symbol可作为对象的属性key名
- 根据规范,Symbol类型可以作为数据单独存在,也可以作为对象的属性key名。并且,对象的属性key只能是字符串类型或者Symbol类型,没有别的数据类型可以作为属性key,Boolean不行,Number也不行。
- 但值得注意的是,需要以
{[SymbolKey]: value}
数组括弧的方式来挂载。
// 作为对象的属性名
let desc = Symbol('desc')
let person = {
name: 'huilin',
sex: '男',
[desc]: '职位:前端工程师'
}
// 或者可以这样赋值:person[desc] = '职位:前端工程师'
console.log(person)
// 输出:{name: 'huilin',sex: '男',Symbol('desc'): '职位:前工程师'}
复制代码
- Symbol作为属性名时具有弱隐藏性
/*
* 常规的方式获取对象属性,会自动忽略Symbol属性的键值对的
*/
// 上面的例子如果进行JSON.stringify()格式化操作,会忽略Symbol
console.log(JSON.stringify(person))
// 输出:{name: 'huilin',sex: '男'}
// 同样的,像for循环这样的常规遍历操作,会忽略Symbol
for(key in person){
console.log(key)
}
// 输出: name sex
// Object.keys()会忽略Symbol
console.log(Object.keys(person))
// 输出: [ 'name', 'sex' ]
// Object.getProperty()忽略Symbol
console.log(Object.getOwnPropertyNames(person))
// 输出:[ 'name', 'sex' ]
/*
* 仅获取Symbol属性的键值对的方法
*/
console.log(Object.getOwnPropertySymbol(person))
// 输出:[ Symbol(desc) ]
/*
* 同时获取常规属性和Symbol属性的方法
*/
console.log(Reflect.ownKeys(person))
// 输出:[ 'name', 'sex', Symbol(desc) ]
复制代码
示例:通过Symbol模拟对象的私有属性或者私有方法
- 借助Symbol属性名的弱隐藏性,模拟私有属性
// Symbol类型的属性名
const id = Symbol()
class User {
constructor(idVal, name, age){
this[id] = idVal
this.name = name
this.age = age
}
checkId(id){
return this[id] === id
}
}
// 私有属性,外部实例不能直接获取
let u = new User('001', 'Jay', 40)
console.log(u.name, u.age, u[id])
// 输出:Jay 40 001
// 但是通过对外暴露的方法,能访问到私有属性
console.log(u.checkId('001')) // true
console.log(u.checkId('002')) // false
复制代码
示例:利用Symbol进行数据归集和整合
- 拿张鑫旭大佬打听小美眉的例子来看,通常情况下两个对象合并,key相同则会覆盖:
let info1 = {
name: '小雪',
age: 24,
job: '前端工程师',
desc: '喜欢看电影,已经有交往对象'
}
let info2 = {
desc: '喜欢小狗,住在南山区,上下班坐公交车'
}
// 由于使用desc是String作为key,key相同会覆盖
console.log(Object.assgin(info1,info2))
// 输出:{name: '小雪',age: 24,job: '前端工程师',desc: '喜欢小狗,住在南山区,上下班坐公交车'}
复制代码
- 那改成用Symbol作为属性key名会怎样呢?Symbol不会进行覆盖的
let info1 = {
name: '小雪',
age: 24,
job: '前端工程师',
[Symbol('desc')]: '喜欢看电影,已经有交往对象'
}
let info2 = {
[Symbol('desc')]: '喜欢小狗,住在南山区,上下班坐公交车'
}
// 通过Symbol作为key,合并会保留
console.log(Object.assgin(info1,info2))
// 输出:{name: '小雪',age: 24,job: '前端工程师',Symbol('desc'): '喜欢看电影,已经有交往对象', Symbol('desc'): '喜欢小狗,住在南山区,上下班坐公交车'}
复制代码
- 可见,Symbol更关注的是value值,而不是key名。可以思考得出,Symbol的特性就是方便对数据进行归集和整合。
- 拿现实中的例子来说吧,微信文章的点赞墙,数据值都是点赞,但记录不会被覆盖,用户的头像都会罗列出来;再比如签到簿,数据值是时间,很有可能是扎堆签到时间一样,但也不会被覆盖,而是把记录罗列进来。
- 再回到JavaScript语法层面,可能大家会觉得,名字冲突这种事情,概率很低吧?有必要专门新增一个Symbol嘛?但是你想啊,ES6的Module,导入导出是可以起别名的;还有ES6的解构,可以直接获取对象的属性名到当前环境;这样你还觉得名字冲突的概率低吗?
- 所以Symbol通过归集和整合的特性,针对基础框架版本升级时,便于同名的方法或者变量向下兼容。
系统Symbol
- 除了自己创建Symbol标记之外,ES6还提供了一系列内置的well-know(众所周知)的Symbol标记,用于改变JavaScript底层API的行为
API | desc |
---|---|
Symbol.hasInstance | 当调用instanceof运算符判断实例时,会调用这个方法 |
Symbol.isConcatSpreadable | 当调用Array.prototype.concat()时,判断是否展开 |
Symbol.unscopables | 对象指定使用with关键字时,哪些属性会被with环境排除 |
Symbol.match | 当执行str.match(obj)时,如果该属性存在会调用它,并返回方法的返回值 |
Symbol.replace | 当执行str.replace(obj)时调用,并返回方法的返回值 |
Symbol.search | 当执行str.search(obj)时调用,并返回方法的返回值 |
Symbol.split | 当执行str.split(obj)时调用,并返回方法的返回值 |
Symbol.iterator | 当对象进行for…of循环时,调用Symbol.iterator方法,返回该对象默认遍历器 |
Symbol.toPrimitive | 当对象被转换为原始数据类型时调用,返回该对象对应的原始数据类型 |
Symbol.toStringTag | 在该对象调用toString方法时调用,返回方法的返回值 |
Symbol.species | 创建衍生对象时使用该属性 |
参考
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END