先简单说说什么是Symbol
- Symbol是ES6新增的基础数据类型,它的特点就是独一无二的,如同UUID一样;
- Symbol是函数,通过调用Symbol函数来创建Symbol数据;
- Symbol还是内置对象,提供一系列函数well-known Symbol方法来改变JS语言的内部行为;
Symbol的特性与使用
示例:创建Symbol数据
- Symbol没有字面量的创建方式,也不能以
new Symbol()构造函数的方式创建,只能通过调用Symbol([description])函数,或者Symbol.for()来创建。
new Symbol()
let symbol1 = Symbol()
let localSymbol Symbol('desc1')
let globalSymbol = Symbol.for('desc1')
console.log(localSymbol === globalSymbol)
特性:Symbol总是唯一的
- 数据总是独一无二的,不仅在函数、模块甚至在window顶层作用域中都是唯一的
let f1 = Symbol('flag')
let f2 = Symbol('flag')
console.log(f1,f2)
console.log(f1 === f2)
console.log(f1 === 'flag')
let lock = Symbol.for('flag')
let lockFlag = Symbol.for('flag')
console.log(lock === lockFlag)
console.log(f1 === lock)
console.log(Symbol.keyFor('flag'))
console.log(Symbol.keyFor('test'))
示例:使用Symbol来定义常量
- 既然Symbol的特性是唯一标志,我们可以用Symbol来做常量。
- 以前我们定义常量是这样婶的:
const FRUIT = {
APPLE: 'APPLE',
BANANA: 'BANANA',
STRAWBERRY: 'STRAWBERRY'
}
console.log(FRUIT.APPLE)
const FRUIT = {
APPLE: 'APPLE',
BANANA: 'BANANA',
STRAWBERRY: 'STRAWBERRY',
PINEAPPLE: 'APPLE'
}
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)
- 但和String类型不一样的是,Symbol不会进行隐式的自动类型转换,所以不能直接进行字符串拼接运算和算术运算。但可以人为的进行显式类型转换,比如转成String、Boolean、Number、Object
let symbolUUID = Symbol('uuid')
console.log(symbolUUID + '测试')
console.log(symbolUUID + 1)
console.log(symbolUUID ? '真' : '假')
console.log(String(symbolUUID) + '测试')
特性:Symbol可作为对象的属性key名
- 根据规范,Symbol类型可以作为数据单独存在,也可以作为对象的属性key名。并且,对象的属性key只能是字符串类型或者Symbol类型,没有别的数据类型可以作为属性key,Boolean不行,Number也不行。
- 但值得注意的是,需要以
{[SymbolKey]: value}数组括弧的方式来挂载。
let desc = Symbol('desc')
let person = {
name: 'huilin',
sex: '男',
[desc]: '职位:前端工程师'
}
console.log(person)
console.log(JSON.stringify(person))
for(key in person){
console.log(key)
}
console.log(Object.keys(person))
console.log(Object.getOwnPropertyNames(person))
console.log(Object.getOwnPropertySymbol(person))
console.log(Reflect.ownKeys(person))
示例:通过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])
console.log(u.checkId('001'))
console.log(u.checkId('002'))
示例:利用Symbol进行数据归集和整合
- 拿张鑫旭大佬打听小美眉的例子来看,通常情况下两个对象合并,key相同则会覆盖:
let info1 = {
name: '小雪',
age: 24,
job: '前端工程师',
desc: '喜欢看电影,已经有交往对象'
}
let info2 = {
desc: '喜欢小狗,住在南山区,上下班坐公交车'
}
console.log(Object.assgin(info1,info2))
- 那改成用Symbol作为属性key名会怎样呢?Symbol不会进行覆盖的
let info1 = {
name: '小雪',
age: 24,
job: '前端工程师',
[Symbol('desc')]: '喜欢看电影,已经有交往对象'
}
let info2 = {
[Symbol('desc')]: '喜欢小狗,住在南山区,上下班坐公交车'
}
console.log(Object.assgin(info1,info2))
- 可见,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 | 创建衍生对象时使用该属性 |
参考