Fork me on GitHub

ES6之Symbol

众所周知,在 JS 中有六种基本数据类型,即五个原始数据类型UndefinedNullBooleanStringNumber和一个对象类型Object

ES6中,又推出了一种新的原始数据类型,就是我们今天博客中要介绍的——Symbol类型。

这种新的数据类型是做什么用的呢?它有哪些需要我们及时了解和掌握的知识和特性呢?

就让我们来一步一步揭开ES6新数据类型Symbol的神秘面纱吧。

Symbol,就是符号,标志的意思。在ES6新出现的Symbol数据类型,主要有以下两个作用:

  • 提供JS中一直比较欠缺的一种特性: 唯一性
  • 为操作JS的相关内部逻辑提供接口,也就是操作一些JS的语言内部行为

当然,第二个作用一般在实际业务和项目中是很难遇到应用的地方的(别告诉我你有在项目代码中进行JS魔改的打算。。。。

所以这篇博客里对Symbol的介绍,也主要侧重于它的第一个作用,也就是它所能提供的唯一性, 具体是个什么东西。

Symbol的创建

创建一个Symbol类型的值的语法如下:

javascript
1
2
Symbol([description])
//description为可选参数。为创建的symbol值提供一个描述

通过Symbol()这个静态函数,我们可以创建一个symbol类型的值。

需要注意的是,跟其他的原始类型不同,创建出来的这个symbol值我们是看不见的,这也是symbol对于很多初学者来说比较难以理解的地方,它并不像一个字符串或者数字一样,非常直观的静静的躺在代码里,它是隐藏在代码后面的。我们知道Symbol()函数返回一个symbol值,我们也可以将这个symbol值赋值给一个变量,但我们并不能真真切切的看到这个symbol值。

1
2
3
4
5
6
7
8
9
let a = Symbol()
// 此时变量a代表的就是我们创建的symbol值,一个独一无二的值。
let b = Symbol()
// 变量b是我们创建的另一个symbol值,它也是独一无二的。
typeof a // symbol
typeof b // symbol
a // 打印到控制台,输出 Symbol()
b // 打印到控制台,同样输出 Symbol()
a === b //false

到这里,我们就应该可以理解symbol值的创建方法了,也明白了我们每次调用Symbol()创建的每个symbol值,都是独一无二,仅此一份的,绝对不会重复。

那问题又来了,虽然我们在上面代码中创建的两个symbol值是不相等的,独一无二的,但打印到控制台,输出的都是Symbol()。我们怎么区分它们呢?谁知道它们是不是代表的同一个symbol

别忘了我们在上面提到的,Symbol函数可以接受一个可选参数description。这个参数,就可以为我们创建的symbol值,提供一个描述,便于我们进行区分。

1
2
3
4
let a = Symbol('a symbol')
let b = Symbol('b symbol')
a // 打印到控制台,输出 Symbol('a symbol')
b // 打印到控制台,输出 Symbol('b symbol')

但要记得我们上面提到的,Symbol()函数每次创建的symbol值都是唯一的,description参数只是提供一个描述。

就算为两次创建的symbol提供相同的描述,它们各自仍然是独一无二的。切记:symbol的唯一性唯一性唯一性。

1
2
3
let a = Symbol('tsymbol')
let b = Symbol('tsymbol')
a === b //false

以上就是symbol的基本概念,关于它,还有以下几个细碎的要点需要注意:

  • 作为原始数据类型,symbol值是不能用 new Symbol()创建的。(你问new Booleannew Number() 为什么可以?额,这属于JS的历史遗留问题,其实它们也不是完全可以,不信你打印一下typeof new Number(22)

  • symbol值不能和其他类型的值进行运算

    1
    2
    3
    4
    5
    let a = Symbol()

    +a //TypeError: can't convert symbol to number
    a + 3 //TypeError: can't convert symbol to string
    a + 'aaa' //TypeError: can't convert symbol to string
  • symbol 可以显式转换为字符串

    1
    2
    3
    4
    let a = Symbol('a')

    String(a) //'Symbol(a)'
    a.toString //'Symbol(a)'
  • symbol可以转换为布尔值

    1
    2
    3
    4
    5
    6
    7
    let a = Symbol('a')

    !a //false
    Boolean(a) //true

    //也可以像下面这样使用(谁会这么用啊摔
    if(a){....}

Symbol的使用

作为属性名使用

在上面提到symbol的唯一性这个特点时,相信很多同学就会想到将其作为属性名使用,从而避免我们经常遇到的,在向对象添加属性时错误覆盖对象原有属性等各种有关属性冲突的问题。

通过将symbol值作为对象的属性名,保证这个对象属性的唯一性,也正是symbol最重要的作用之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
let star = Symbol('star')

//第一种方法
let obj = {}
obj[star] = "hello symbol"

//第二种方法
let obj = {
[star]: "hello symbol"
}

//第三种方法
Object.defineProperty(a, star, {value: "hello world"})

需要注意的是,对象以symbol作为属性名时,访问这个属性必须要使用方括号形式,而不能使用点运算符形式,因为点运算符会始终使用它后面跟随属性名的字符串形式值。

1
2
3
obj[star]    //'hello symbol'
obj.star // undefined
obj['star'] //undefined

另外,对象以symbol作为属性名时,该属性依然是公开属性,但不会被for...infor...of循环到,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。

要想遍历出对象属性名为symbol值类型的属性,需要使用Object.getOwnPropertySymbols()方法,它返回所有symbol类型属性名的一个数组。

作为常量使用

很多时候,我们需要为对象设置一系列属性用于对应不同的类型,在使用时,我们并不在意这些属性的具体值,只需要保证它们是可区分的各自唯一值即可。此时我们可以使用symbol作为我们的属性值,从而保证多个属性值间的唯一性。

1
2
3
4
5
6
7
8
9
10
11
let type = {
car: Symbol('car'),
bicycle: Symbol('bicycle'),
plane: Symbol('plane')
}

function traffic(yourTraffic){
if(yourTraffic === type.car){...}
else if(yourTraffic === type.bicycle){...}
else if(yourTraffic === type.plane){...}
}

这样,我们就不需要使用一堆字符串,来区分对象多个属性之间的值了。

Symbol的静态方法

Symbol有两个方法,分别是Symbol.for(key)Symbol.keyFor(sym)

Symbol.for(key)

Symbol.for(key)方法同样是用于创建一个symbol值,但它接受一个key值。如果我们使用相同的key创建过一个symbol值,它就会直接返回这个symbol值,否则就会将创建一个新的以key为索引登记在全局注册表中的symbol值。

Symbol的全局注册表,就是使用Symbol.for(key)方法创建的所有symbol的登记薄,它存在于全局环境,甚至可以跨越iframe的限制。为我们复用具有唯一性的symbol值提供了非常好的支持。

需要注意,使用Symbol()创建的symbol是不会被登记到全局注册表的,它每次都返回一个新的symbol值。

Symbol.keyFor(sym)

很显然,这个方法就是Symbol.for(key)的逆方法,它接受一个symbol值作为参数,并返回这个symbol在全局注册表中对应的key值。如果没有对应的key,它就返回个undefine(要不它还能返回什么。。。?

通过这两个方法的相互配合,我们对Symbol的基本使用能够形成一个闭环。

Symbol的静态属性

Symbol的静态属性,主要用来提供给我们一些操纵js内部语言行为的一些接口。例如Symbol.replace,就是用于修改字符串的String.prototype.replace()方法的。当一个对象被String.prototype.replace()方法调用时,会使用对象的Symbol.replace属性对应的方法代替。从而达到修改js内部语言实现的目的。

1
2
3
4
5
let aStr = 'some string'

String.prototype.search(aStr,'some')
//等于以下代码
aStr[Symbol.replace](this,'some')

具体的,我们就可以写出下面这样的代码。

1
2
3
4
5
6
let x = {};

//我们可以修改replace的行为
x[Symbol.replace] = (str,newstr) => {console.log(str,newstr)}

"hello".replace(x,"world") //hello

当然,这种Symbol的使用方式很少被用到,所以在此也不再赘述。有想要了解的同学,为大家提供以下两个链接来对Symbol做更深入的学习。

好啦,关于ES6Symbol就为大家介绍到这里啦。谢谢,鞠躬退场^-^。

----本文结束感谢阅读----