ES6 - Symbol

414 阅读5分钟

一种新的原始数据类型

定义

它的意思是符号、独一无二的意思,可以理解为不能重复的字符串

原始数据类型

  • number -string,
  • bool
  • null,
  • undefined,
  • object,
  • Symbol

声明方式

不加任何描述信息的Symbol

// Symbol 表示的是独一无二的象征
let s1 = Symbol()
let s2 = Symbol()
console.log(s1);                   // Symbol()             
console.log(s2);                   // Symbol()    
console.log(s1 === s2);            // false

这种方式由于没有任何的描述,因此并不知道s1、s2是什么

将对象作为参数来作为Symbol的描述信息

把一个字符串作为参数传进去,用于对Symbol的描述

let s1 = Symbol('foo')
let s2 = Symbol('bar')
console.log(s1);               // Symbol(foo)
console.log(s2);               // Symbol(bar)
console.log(s1 === s2);        // false

将对象作为参数来作为Symbol的描述信息

参数是对象的时候会自动调用该对象的toString方法,将它转成字符串,然后把这字符串作为Symbol的描述

let obj = {
    name: 'lee'
}

let s = Symbol(obj)
console.log(s);        // Symbol([object Object])

let obj = {
    name: 'lee',
    toString(){
        return this.name
    }
}

let s = Symbol(obj)
console.log(s);        // Symbol(lee)

Symbol 不是对象

不能拿对象的方式去对待symbol,它更多的是一种特殊的不能重复的字符串

// Symbl不是对象
let s = Symbol()
s.name = 'lee'
console.log(s);          // Symbol()

// Symbol前也不能使用new
let s = new Symbol()       // Symbol is not a constructor

symbol.dscription 输出symbol的描述信息

通过Symbol.for()来声明

let s1 = Symbol.for('foo')
console.log(s1);            // Symbol(foo)

发现结果和普通的Symbol声明方式一样,那么去区别是什么呢?

let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
console.log(s1 === s2);         // true

let s1 = Symbol('foo')
let s2 = Symbol('foo')
console.log(s1 === s2);         // false

为什么通过Symbol.for的方式创建的symbol值就相等,而普通的方式创建的Symbol值就不相等呢?

===> 通过Symbol.for创建的symbl的值实际上是定义在全局的环境中,当第二次声明Symbol.for('foo')的时候就会去全局中找,前面是否声明过描述叫foo的,那么下一个就指向上一个,因此s1和s2是同一个

===> 通过Symbol('foo')的方式创建symbol的时候就不是在全局环境下创建的,而是每次都是申明一个新的描述信息叫foo的symbol

如果使用Symbol.for('foo')创建100个描述信息叫foo的symbol实际上就只声明了一次

如果使用Symbol('foo')创建100个描述信息叫foo的symbol实际上就声明了100次

通过Symbol.for('foo')创建的symbol就已经是全局环境,并不管当前的Symbol.for('foo')是否在全局环境下声明的

// 即使Symbol.for定义在函数中,实际上它也是全局的
function foo() {
    return Symbol.for('foo')
}

const x = foo()
const y = Symbol.for('foo')
console.log(x === y);            // true
 

Symbol.keyFor()

返回一个已经在全局登记的Symbl类型的key / 或者说描述信息

  • 通过Symbol.for('foo')声明的symbol可以找到key
  • 但是通过Symbol('foo')普通的方式声明的symbol就找不到key(因为它不是声明在全局环境下)
const s1 = Symbol('foo')
console.log(Symbol.keyFor(s1));      // undefined

const s2 = Symbol.for('foo')
console.log(Symbol.keyFor(s2));      // foo

应用场景

1. 把Symbol作为对象的key,以保证当前的key并不产生冲突

相同的key,后定义的会覆盖先定义的

例如: 班级中存在重名的2个同学,这2个人只是名字一样,但是它表示2个不同的人

// 3个人,只输出2个人,后定义的会覆盖先定义的人
const grade = {
    张三: {address:'xxx', tel: '183xxx'},
    李四: {address:'yyy', tel: '151xxx'},
    李四: {address:'zzz', tel: '135xxx'}
}
console.log(grade);                 // {张三: {address:'xxx', tel: '183xxx'},李四: {address:'zzz', tel: '135xxx'}}

这样不满足需求!!!

将对象的key改成变量呢?

对象的key也可以是一个变量

// 还是被认为是相同的key,还是会出现覆盖
const stu1 = '李四'
const stu2 = '李四'
const grade = {
    [stu1]: { address: 'yyy', tel: '151xxx' },
    [stu2]: { address: 'zzz', tel: '135xxx' }
}
console.log(grade);   // { address: 'zzz', tel: '135xxx' }

这样也不行

使用Symbol来做

const stu1 = Symbol('李四') 
const stu2 = Symbol('李四') 
const grade = {
    [stu1]: { address: 'yyy', tel: '151xxx' },
    [stu2]: { address: 'zzz', tel: '135xxx' }
}
console.log(grade);   // { address: 'zzz', tel: '135xxx' }

image.png

这样,对象的key的类型由只能是字符串变为也可以是Symbol类型,凡是Symbol定义的属性都表示独一无二的,这样就可以保证和其他的名字产生冲突

2. 在一定程度上将类class中的某些属性保护起来(当然也有办法读到它)

思路:父类中定义Symbol作为key,用一般的遍历的方式遍历对象的实例是取不到key的

const sym = Symbol('symbol')
class User {
    constructor(name) {
        this.name = name
        this[sym] = 'symbol'
    }
    getName() {
        return this.name + this[sym]
    }
}

const user = new User('lee')
console.log(user.getName());           // lee symbol

for(let key in user){
    console.log(key);                 // name
}

for(let key of Object.keys(user)){
    console.log(key);                // name
}

for(let key of Object.getOwnPropertySymbols(user)){
    console.log(key);                // Symbol(symbol)
}

for(let key of Reflect.ownKeys(user)){
    console.log(key);                // name Symbol(symbol)
}

3. 用Symbol消除魔术字符串(不关系对象key对应的value)

代码中多次出现相同的字符串(让代码形成强耦合),很多地方都要引用,手敲的话可能还会敲错

// 如下面的字符串‘Triangle’就被在多个地方使用
function getArea(shape){
    let area = 0
    switch(shape) {
        case 'Triangle':
            area = 1
            break
        case 'Circel':
            area = 2
            break
    }
    return area
}

console.log(getArea('Triangle')); 

那么有什么办法能只写一遍呢?

const shapeType = {
    triangle: 'Triangle',
    circle: 'Circle'
}
function getArea(shape){
    let area = 0
    switch(shape) {
        case shapeType.triangle:
            area = 1
            break
        case shapeType.circle:
            area = 2
            break
    }
    return area
}

console.log(getArea(shapeType.triangle)); 

上面的方法在一定程度上消除了“魔术字符串”

对于shapeType对象来说,我们好像并不关心属性triangle后面对应的值到底是什么样的值,我们只要让属性名triangle和属性名circle不产生冲突就行

const shapeType = {
    triangle: Symbol(),
    circle: Symbol()
}
function getArea(shape){
    let area = 0
    switch(shape) {
        case shapeType.triangle:
            area = 1
            break
        case shapeType.circle:
            area = 2
            break
    }
    return area
}

console.log(getArea(shapeType.triangle));  // 1