我的学习记录
什么是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'