ES6中新增的第7种基本数据类型 —— Symbol

258 阅读7分钟

Symbol

​ 之前我们的对象属性的数据类型都是字符串,没有其他的了, 但是这样会存在属性名重复的问题,这也就导致了后设置的属性名会把之前同名属性名对应的值给覆盖掉,也就是会产生变量名污染的问题。

​ 比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,在添加的操作就很容易覆盖了原有的方法

​ 现在ES6中产生了一种新的数据类型,叫做Symbol,其表示的是独一无二的数据值,

基本创建

// Symbol是ES6中新增的基本数据类型
// 表示的是独一无二的数据
// 其和任意数据都不相同,包括它自己本身

// 可以使用Symbol函数来创建一个symbol类型的值
// 注意是函数,不是构造函数,所以不需要加上newnew
let s1 = Symbol()
// 注意:输出的不是字符串
// (字符串在console中是黑色的,但是这里的输出是红色的)
console.log(s1) // => Symbol()

let s2 = Symbol()
console.log(s2) // => Symbol()


// 1. s1 和 s2 虽然输出的内容是一致的
// 但是他们是不同的2个值,输出一样的内容只是为了隐藏内部的具体结果
console.log(s1 === s2) // => error 
// 执行上一步的时候,可能在控制台中会报错,那是因为symbol是恒不等于symbol的,所以tsc在编译的时候就已经抛出了警告

注意: 如果需要在ts中使用es6中的浏览器无法识别的语法的时候,需要在compilerOptions选项下的lib数组中加上es6或者是es2015

同理,如果需要在ts中操作dom的时候,需要在lib数组中配置dom

sHMN7T.png

// Symbol函数中可以传递一个字符串作为标识
// 注意: 这个参数的作用仅仅只是作为标识和提示的作用
let s1 = Symbol('demo')
console.log(s1) // => Symbol('demo')

let s2 = Symbol('demo')

// s1 和 s2 依旧是不想等的
// 因为Symbol所表示的值,都是独一无二的值
console.log(s1 === s2) // => false
// 如果在Symbol函数中传入标识的时候
// 如果这个标识不是字符串,那么其会默认将其转换为字符串后再进行创建symbol类型的数据
let s1 = Symbol('s1')
let s2 = Symbol(1) // <=> let s2 = Symbol('1')
let s3 = Symbol({ name: 'Klaus' }) // <=> let s3 = Symbol('[object Object]')

// 但是值得注意的是,ts中symbol中可以传递的值只能是string | number
// 但是在js中symbol函数的标识,可以是任意类型的合法数据
// Symbol类型的数据,是不可以和其它数据类型的值进行运算的
let s = Symbol()

console.log(s + 1) // -> error

// Symbol类型的值是可被转换为字符串
console.log(s.toString())

// symbol类型的值是可以转换为boolean值
// symbol都是有值的,所以其转换为boolean值的时候
// 其结果都是true
console.log(!!s) // => true
// Symbol 可以作为对象的属性名存在
let prop = Symbol('name')

const obj = {
    [prop]: 'Klaus'
}

// 以symbol类型的值作为属性名的多数为私有属性
// 在模块内部可以使用存储symbol值的变量名进行访问
// 在模块外部,如果这个存储symbol值的变量名没有向外进行暴露的话
// 其是无法获取到对应的私有属性的
console.log((<object> obj)[prop])
let o = {
    name: 'Klaus',
    [Symbol('age')]: 23,
    gender: 'male'
}

// 以下四种情况是无法遍历出symbol类型的数据的

// 1. for ... in ...
for (const key in o) { 
    console.log(key)
}


// 2. Object.keys()
console.log(Object.keys(o))

// 3. Object.getOwnPorpertyNames
console.log(Object.getOwnPropertyNames(o))

// 3. JSON.stringify()
console.log(JSON.stringify(o))


// 可以通过以下2种情况来访问到symbol类型的数据

// 1. getOwnPorpertySymbols
console.log(Object.getOwnPropertySymbols(o))

// 2. Reflact.ownKeys()
console.log(Reflect.ownKeys(o))

Symbol的2个静态方法

// Symbol.for
let s1 = Symbol.for('Klaus')
let s2 = Symbol.for('Klaus')

// 输出的内容和使用Symbol函数创建的结果一致
console.log(s1) // => Symbol('Klaus')
console.log(s2) // => Symbol('Klaus')


// 区别在于
// 使用Symbol.for()创建的Symbol会去 全局 查找一下有没有使用
// 所传入的字符串作为标识而创建的symbolsymbol
//      如果存在,那么就直接取之前所创建的symbol值返回
//      如果不存在,那么就新创建一个,在将结果返回
console.log(s1 === s2) // => true

// 其次Symbol.for() 必须传递一个参数,作为symbol的标识
// 但是Symbol() 不一定需要传递字符串标识
let s3 = Symbol.for('') // -> Success
let s4 = Symbol.for('Klaus') // -> Succsee
let s5 = Symbol.for() // -> error
 // Symbol.keyfor
//  这个函数传入一个symbol对象
//  返回值是使用Symbol.for创建的symbol的标识字符串字符串

let s1 = Symbol.for('Klaus')

console.log(Symbol.keyFor(s1)) // => Klaus

let s2 = Symbol()
console.log(Symbol.keyFor(s2)) // => undefined

let s3 = Symbol('Klaus')
console.log(Symbol.keyFor(s2)) // => undefined

10个内置的Symbol值 (了解)

// hasInstance

// Symbol.hasInstance 指向是js中预先设置的值
const o = {
    [Symbol.hasInstance]: (o) => { 
        console.log(o)
    }
}

// 在调用instanceof方法的时候
// 1. 先调用Symbol.hasInstance所设置的方法
// 2. 调用instanceof原本的功能
console.log({ name: 'Klaus' } instanceof <any>o)
/**
 * =>
 * { name: 'Klaus' }
 * false
 */
// isConcatSpreadable
// 这是数组的一个可读写属性
// 默认值是undefined

const arr = [1, 2, 3]

// 如何不设置或者设置为true
// 在使用concat进行数组拼接数组的是会进行自动展开
console.log(arr.concat([3, 4])) // => [1, 2, 3, 4, 5]
console.log(arr[Symbol.isConcatSpreadable]) // => undefined

// 设置为false以后就不会自动展开了
arr[Symbol.isConcatSpreadable] = false
console.log(arr.concat([3, 4])) // => [[1, 2, 3], 4, 5]

Symbol.species

// JS运行环境下
class C extends Array {
    constructor(...args) { 
        super(...args)
    } 

    // 定义一个实例方法,这个方法会被挂载到C的实例的原型对象上
    getName() { 
        return 'Klaus'
    }
}

const c = new C(1, 2, 3)

const newArr = c.map(item => item + 1) // 这里newArr是通过实例对象c所创建出来的对象
// 可以认为newArr是c的衍生对象


console.log(c instanceof C) // => true
console.log(c instanceof Array) // => true

console.log(newArr instanceof C) // => true
console.log(newArr instanceof Array) // => true
// ts运行环境下
class C extends Array {
    // 因为默认的构造器是无参的
    // 但是实际是继承了Array,所以其是在ts中是需要重写其构造函数的
    // 但是在js中最好重写构造函数,但是不写也是可以的,因为会自动容错
    constructor(...args) { 
        super(...args)
    } 

    // 定义一个实例方法,这个方法会被挂载到C的实例的原型对象上
    getName() { 
        return 'Klaus'
    }
}

const c = new C(1, 2, 3)

const newArr = c.map(item => item + 1) // 这里newArr是通过实例对象c所创建出来的对象
// 可以认为newArr是c的衍生对象


console.log(c instanceof C) // => false 这是ts环境下和js不同的地方
console.log(c instanceof Array) // => true

console.log(newArr instanceof C) // => false 这是ts环境下和js不同的地方
console.log(newArr instanceof Array) // => true
// js运行环境下
class C extends Array {
    constructor(...args) { 
        super(...args)
    } 

    // 这个是定义在类上的静态方法方法
    // 这个方法是get方法
    // 表示的是在创建实例,获取实例对象的时候
    // 会自动先调用你所设置的方法后在执行原本的默认行为
    static get [Symbol.species]() { 
        return Array // 在这里 设置所有的C的实例的构造函数都是 Array
    }

    getName() { 
        return 'Klaus'
    }
}

const c = new C(1, 2, 3)

const newArr = c.map(item => item + 1)

console.log(c instanceof C)  // => true
console.log(c instanceof Array)  // => true

console.log(newArr instanceof C)  // => false 因为此时的构造函数是Array
console.log(newArr instanceof Array)  // => true
// ts运行环境下
class C extends Array {
    constructor(...args) { 
        super(...args)
    } 

    static get [Symbol.species]() { 
        return Array
    }

    getName() { 
        return 'Klaus'
    }
}

const c = new C(1, 2, 3)

const newArr = c.map(item => item + 1)

console.log(c instanceof C)  // => false 特别的,在ts中这里返回的是false
console.log(c instanceof Array)  // => true

console.log(newArr instanceof C)  // => false 因为此时的构造函数是Array
console.log(newArr instanceof Array)  // => true
// 字符串函数中的4个内置symbol值

// Symbol.match ==> 在调用字符串的match方法的时候会执行
// Symbol.split ==> 在调用字符串的split方法的时候会执行
// Symbol.replace ==> 在调用字符串的replace方法的时候会执行
// Symbol.search ==> 在调用字符串的search方法的时候会执行

let o = {
    // 以下4个方法中str都是函数调用时候,调用字符串
    [Symbol.match](str) {
        console.log('match', str.length)
    },

    [Symbol.split](str) {
        console.log('split', str.length)
    },

    [Symbol.replace](str) {
        console.log('replace', str.length)
    },

    [Symbol.search](str) {
        console.log('search', str.length)
     }
}


'abcde'.match(<any>o) // => match 5
'abcde'.split(<any>o) // => split 5 
'abcde'.replace(<any>o, 'a') // => replace 5
'abcde'.replace('a', <any>o) // => replace 5
'abcde'.search(<any>o) // => search 5
let o = {
   [Symbol.toPrimitive](type) { 
       // 这个函数会在对象被转换为基本数据类型的时候被调用
       // 参数type是被转换的哪一个类型
       console.log(type)
   }
}

console.log(`${o}`) // 这里会将对象转换为了字符串(基本数据类型)
// 所以会调用[Symbol.toPrimitive]函数
// 在ts中输出的结果为default
// 在js中输出的结果为string
let o: unknown = {
    [Symbol.toPrimitive](type) { 
        // 这个函数会在对象被转换为基本数据类型的时候被调用
        // 参数type是被转换的哪一个类型
        console.log(type)
    }
}

// 因为对象无法转换为数字,转换后的结果为NaN
// 所以在这里需要将o设置为unknown或any 才不会标红 .

/**
 * number
 * NaN
 */
console.log((o as number)++) 
// Symbol.toStringTag  调用toString方法的时候会调用的值
// Symbol.toStringTag 的值可以是字符串,也可以是一个对象存储器的get方法
let o1 = {
    [Symbol.toStringTag]: 'Klaus'
}

console.log(o1.toString()) // 会使用[object Klaus] 取代 [object Object]

let o2 = {
    get [Symbol.toStringTag]() { 
        // 需要返回一个字符串
        return 'Klaus'
    }
}

console.log(o2.toString()) // 会使用[object Klaus] 取代 [object Object]