ES6 Symbol的特性与思考

先简单说说什么是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 建立衍生对象时使用该属性

参考

相关文章
相关标签/搜索