继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

初识JS的Symbol数据类型以及它的使用场景

游魂Andy
关注TA
已关注
手记 26
粉丝 27
获赞 179

图片描述

什么是 Symbol

Symbol作为ES6 新增原始数据类型的一种,表示独一无二的值。

回忆一下原始类型的范畴(string, number, boolean, null, undefined, symbol)。

Symbol的使用

创建一个Symbol

const a = Symbol()
console.log(typeof a) // symbol

需要注意的是通过 Symbol 方法创建值的时候不用使用 new 操作符,原因是通过 new 实例化的结果是一个 object 对象,而不是原始类型的 symbol。

const a = new Symbol()
console.log(typeof a) // Symbol is not a constructor

通常使用new来构造是想要得到一个包装对象,而Symbol不允许这么做,那么如果我们想要得到一个Symbol()的对象形式,可以使用Object()函数。

const a = Symbol()
const b = Object(a)
console.log(typeof b) // object

Symbol 方法接收一个参数,表示对生成的 symbol 值的一种描述。

const a = Symbol('a')
const b = Symbol('b')

即使是传入相同的参数,生成的 symbol 值也是不相等的,因为 Symbol 本来就是独一无二的意思。

const a = Symbol('foo')
const b = Symbol('foo')

console.log(a === b) // false

Symbol的应用

Symbol的应用其实利用了唯一性的特性。

作为对象的属性

大家有没有想过,如果我们在不了解一个对象的时候,想为其添加一个方法或者属性,又怕键名重复引起覆盖的问题,而这个时候我们就需要一个唯一性的键来解决这个问题,于是Symbol出场了,它可以作为对象的属性的键,并避免冲突。

// 创建一个`Symbol`
const a = Symbol()

// 创建一个对象
const obj = {}

// 通过`obj[]`将`Symbol`作为对象的键
obj[a] = 'hello world'

值得注意的是我们无法使用.来调用对象的Symbol属性,所以必须使用[]来访问Symbol属性

降低代码耦合

代码千万行,维护第一难。编码不规范,同事两行泪。

当代码中充斥着大量的魔法字符时,纵使是原开发者在经过一段时间后再回头看也会变得难以理解,更不必说是交由后来开发者维护。

假如现有一个 Tabs 切换的功能:

if (type === 'basic') {
    return <div>basic tab</div>
}

if (type === 'super') {
    return <div>super tab</div>
}

上面代码中字符串 basic、super 就是与业务代码无关的魔法字符,接下来使用 Symbol 对这块代码进行改造。

const tabTypes = {
    basic: Symbol(),
    super: Symbol(),
}

if (type === tabTypes.basic) {
    return <div>basic tab</div>
}

if (type === tabTypes.super) {
    return <div>super tab</div>
}

模拟类的私有方法

ES6 中的类是没有 private 关键字来声明类的私有方法和私有变量的,但是我们可以利用 Symbol 的唯一性来模拟。

const speak = Symbol()
class Person {
    [speak]() {
        ...
    }
}

因为使用者无法在外部创建出一个相同的 speak,所以就无法调用该方法。

全局共享Symbol

如果我们想在不同的地方调用已经同一Symbol即全局共享的Symbol,可以通过Symbol.for()方法,参数为创建时传入的描述字符串,该方法可以遍历全局注册表中的的Symbol,当搜索到相同描述,那么会调用这个Symbol,如果没有搜索到,就会创建一个新的Symbol

为了更好地理解,请看下面例子

const a = Symbol.for('a')
const b = Symbol.for('a')
a === b // true

如上创建Symbol

  1. 首先通过Symbol.for()在全局注册表中寻找描述为aSymbol,而目前没有符合条件的Symbol,所以创建了一个描述为aSymbol
  2. 当声明b并使用Symbol.for()在全局注册表中寻找描述为aSymbol,找到并赋值
  3. 比较ab结果为true反映了Symbol.for()的作用

再来看看下面这段代码

const a = Symbol('a')
const b = Symbol.for('a')
a === b // false

结果竟然是false,与上面的区别仅仅在于第一个Symbol的创建方式,我们来一步一步分析一下为什么会出现这样的结果。

  1. 使用Symbol('a')直接创建,所以该Symbol('a')不在全局注册表中
  2. 使用Symbol.for('a')在全局注册表中寻找描述为aSymbol,并没有找到,所以在全局注册表中又创建了一个描述为a的新的Symbol
  3. 秉承Symbol创建的唯一特性,所以ab创建的Symbol不同,结果为false

问题又又又来了!我们如何去判断我们的Symbol是否在全局注册表中呢?

Symbol.keyFor()帮我们解决了这个问题,他可以通过变量名查询该变量名对应的Symbol是否在全局注册表中(Symbol.for创建的)

// Symbol.keyFor 方法返回一个使用 Symbol.for 方法创建的 symbol 值的 key
const a = Symbol('a')
const b = Symbol.for('a')
Symbol.keyFor(a) // undefined
Symbol.keyFor(b) // 'a'

内置Symbol值又是什么?

上面的Symbol使用是我们自定义的,而JS又内置了Symbol值,个人的理解为:由于唯一性特点,在对象内,作为一个唯一性的键并对应着一个方法,在对象调用某方法的时候会调用这个Symbol值对应的方法,并且我们还可以通过更改内置Symbol值对应的方法来达到更改外部方法作用的效果。

为了更好地理解上面这一大段话,咱们以Symbol.hasInstance作为例子来看看内置Symbol到底是个啥!

class demo {
    static [Symbol.hasInstance](item) {
        return item === '游魂博客'
    }
}
"游魂博客" instanceof demo // true

Symbol.hasInstance对应的外部方法是instanceof经常用于判断类型。上面代码创建了一个demo类,并重写了Symbol.hasInstance,所以其对应的instanceof行为也会发生改变,其内部的机制是这样的:当我们调用instanceof方法的时候,内部对应调用Symbol.hasInstance对应的方法即return item === '游魂博客'

obj中Symbol key的获取

es6针对这个,添加了Object.getOwnPropertySymbols方法。

let uid = Symbol('uid')
let obj = {
    [uid]: 'uid'
}

console.log(Object.keys(obj)) // []
console.log(Object.getOwnPropertyNames(obj)) // []
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(uid)]

Symbol 不可强制转换

let uid = Symbol('uid')
uid + ''

这里会报错,根据规范,他会把uid转换成字符串进行相加。如果真的相加,可以先String(uid)之后再相加,不过目前看来,似乎没什么意义。

这里只是介绍了Symbol的一些基础用法,其他使用请参考文档:MDN

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP