ES6之Symbol

339 阅读4分钟

我的学习记录

什么是Symbol?

来源

ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法,新方法的名字就有可能和现有的方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这就从根本上防止了属性名的冲突。

这就是ES6引入Symbol的原因。

语法

Symbol是一种基本数据类型。

语法:

Symbol([description])

description是可选的,字符串类型。为对symbol的描述,可用于调试但不是访问symbol本身。主要是为了在控制台显示,或者转为字符串时比较容易区分。

Symbol()函数会返回symbol类型的值。

const sym1 = Symbol()
const sym2 = Symbol('foo')
const sym3 = Symbol('foo')
typeof sym1                                     // "symbol"
sym1.toString()                                 // "Symbol()"
sym2.toString()                                 // "Symbol(foo)"
sym2.description                                // 'foo'

Symbol("foo") 不会强制将字符串 “foo” 转换成symbol类型,它每次都会创建一个新的 symbol类型,每个从Symbol()返回的symbol值都是唯一的

sym2 === sym3                                   // false
sym2.toString() === sym3.toString()             // true

如果Symbol的参数是一个对象,就会调用该对象的toString方法,将其转换为字符串,然后才生成一个Symbol值。

const obj = {
    toString() {
        return 'abc'
    }
}
const sym = Symbol(obj)
sym                                             // "Symbol(abc)"

不能使用new命令创建symbol数据。 这会阻止创建一个显示的Symbol包装器对象而不是一个Symbol值。

let sym = new Symbol()
// Uncaught TypeError: Symbol is not a constructor

围绕原始数据类型创建一个显式包装器对象从 ECMAScript 6 开始不再被支持。 然而,现有的原始包装器对象,如new Boolean、new String以及new Number,因为遗留原因仍可被创建。

如果你真的想创建一个 Symbol 包装器对象 (Symbol wrapper object),你可以使用 Object() 函数:

var sym = Symbol('foo')
typeof sym                                      // "symbol"
var symObj = Object(sym)
typeof symObj                                   // "object"

一个symbol值能作为对象属性的标识符,这是该数据类型仅有的目的。

let obj = {}
obj[Symbol()] = 'a'
obj                                     // {Symbol(): "a"}

// 本意是想修改这个Symbol()键对应的值
obj[Symbol()] = 'b'

// 结果发现给obj新增了属性
// 这是因为每次Symbol()都是在新建一个Symbol值,它们是唯一的
obj                                     // {Symbol(): "a", Symbol(): "b"}

// 所以要想修改Symbol()键的值,只能将Symbol()赋给一个变量,在通过这个变量去修改对应的值
let sym = Symbol()
obj[sym] = 2
obj                         // {Symbol(): "a", Symbol(): "b", Symbol(): 2}
obj[sym] = 3
obj                         // {Symbol(): "a", Symbol(): "b", Symbol(): 3}

注意,Symbol值作为对象属性名时,不能用点运算符。 因为点运算符后面总是字符串,所以不会读取sym作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个Symbol值。

let sym = Symbol()
const a = {}
a.mySymbol = 'Hello'
a[mySymbol]                             // undefiend
a['mySymbol']                           // 'Hello'

Symbol类型转换

使用宽松相等时:

var sym = Symbol('a')
Object(sym) == sym                  // true

不允许将symbol值隐式的创建一个string类型的属性名:

sym + 'convert to string'
// Uncaught TypeError: Cannot convert a Symbol value to a string

但是,Symbol值可以显示的转为字符串。

let sym = Symbol('tostring')
String(sym)                                 // "Symbol(tostring)"
sym.toString()                              // "Symbol(tostring)"

另外,Symbol值也可以转为布尔值,但是不能转为数值。

let sym = Symbol()
Boolean(sym)                                                // true
!sym                                                        // false

Number(sym) 
// Uncaught TypeError: Cannot convert a Symbol value to a number

Symbol 与 for...in 迭代

Symbol在for...in迭代中不可枚举。

var obj = {}
obj[Symbol('a')] = 'a'
obj[Symbol.for('b')] = 'b'
obj['c'] = 'c'
obj.d = 'd'
for(var i in obj) {
    console.log(i)                                  // c,d
}

Object.getOwnPropertyNames()不会返回symbol对象的属性,但是你能使用Object.getOwnPropertySymbols()得到它们。

Object.getOwnPropertySymbols()方法返回一个给定对象自身的所有Symbol属性的数组。

var obj = {}
var a = Symbol('a')
var b = Symbol.for('b')
obj[a] = 'localSymbol'
obj[b] = 'globalSymbol'
var objectSymbols = Object.getOwnPropertySymbols(obj)
objectSymbols                                       // [Symbol[a], Symbol[b]]
typeof objectSymbols[0]                             // 'symbol'

Symbols 与 JSON.stringify()

当使用JSON.strIngify()时,以symbol值作为键的属性会被完全忽略:

JSON.stringify({[Symbol('foo')]: 'foo'})            // '{}'

Symbol包装器对象作为属性的键

当一个Symbol包装器对象作为一个属性的键时,这个对象将被强制转换为它包装过的symbol值:

var sym = Symbol('foo')
var obj = {
    [sym]: 1
}
obj[sym]                                            //1
obj[Object(sym)]                                    //1

方法

Symbol()

每次都会创建新的symbol

Symbol.for(key)

根据给定的键key,从运行时的symbol注册表中找到对应的symbol,如果找到了,则返回它,否则,新建一个与该键关联的symbol,并放入全局symbol注册表中。

// 创建一个symbol并放入symbol注册表中,键为"foo"
Symbol.for ('foo')
// 从symbol注册表中读取键为"foo"的symbol
Symbol.for("foo")

Symbol.for('foo') === Symbol.for ('foo')                // true
Symbol("bar") === Symbol("bar")                         // false

注册表中的键名,也是该symbol自身的描述字符串。

为了防止冲突,最好给你要放入symbol注册表中的symbol带上键前缀。

Symbol.for("mdn.foo")
Symbol.for("mdn.bar")

Symbol.keyFor(sym)

获取symbol注册表中与某个symbol关联的键。如果全局注册表中查找到该symbol,则返回该symbol的key值,形式为string。如果symbol未在注册表中,返回undefined。

var globalSym = Symbol.for('foo')
Symbol.keyFor(globalSym)                                // 'foo'

var localSym = Symbol('foo')
Symbol.keyFor(localSym)                                 // 'undefined'