前言
ES5的对象属性名都是字符串,这样容易造成属性名冲突。
ES6新增了一种新的原始数据类型Symbol
基本使用
创建Symbol实例使用Symbol函数
每一个 Symbol 值都是不相等的
如果 Symbol 的参数是一个对象,就会调用该对象的
toString方法,将其转为字符串,然后才生成一个 Symbol 值。
let s1 = Symbol('a') // Symbol(a)
let s2 = Symbol('a') // // Symbol(a)
console.log(s1 === s2) // false
let s3 = Symbol({ a: '1' }) // // Symbol([object Object])
let s4 = Symbol(undefined) // Symbol()
let s5 = Symbol(null) // Symbol(null)
const obj1 = {
toString() {
return 'abc'
}
}
let s6 = Symbol(obj1) // Symbol(abc)
ES2019 新增description的属性
let s7 = Symbol('abc')
console.log(s7.toString(s7)) // Symbol(abc)
console.log(s7.description) // abc
Symbol 共享体系
上面使用 Symbol() 函数的语法,不会在你的整个代码库中创建一个可用的全局的 symbol 类型。要创建跨文件可用的 symbol,甚至跨域(每个都有它自己的全局作用域),使用 Symbol.for() 方法和 Symbol.keyFor() 方法从全局的 symbol 注册表设置和取得 symbol。
注意,
Symbol.for()为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行。
Symbol.for()的这个全局登记特性,可以用在不同的 iframe 或 service worker 中取到同一个值。
例子:
Symbol.for('abc') === Symbol.for('abc') // true
Symbol('abc') === Symbol('abc') //false
keyFor:
const s1 = Symbol.for('abc')
const k1 = Symbol.keyFor(s1) // abc
function foo() {
// 在函数内部运行的,但是生成的 Symbol 值是登记在全局环境的
return Symbol.for('abc');
}
const x = foo();
const y = Symbol.for('abc');
console.log(x === y); // true
属性名的遍历
Symbol 作为属性名,遍历对象的时候,该属性不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回 。
可以使用Object.getOwnPropertySymbols()方法,获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
const a = Symbol('a')
const b = Symbol('b')
const obj1 = {}
obj1[a] = 'aa'
obj1[b] = 'bb'
const objectSymbols = Object.getOwnPropertySymbols(obj1) // [ Symbol(a), Symbol(b) ]
应用场景:自定义私有属性
function Fn() {
const test = Symbol('test')
const obj = {}
obj[test] = 'test'
return obj
}
const obj1 = Fn()
console.log(obj1[Symbol('test')]) // undefined
内置通用(well-known)Symbol
除了自己创建的 symbol,JavaScript 还内建了一些在 ECMAScript 5 之前没有暴露给开发者的 symbol,它们代表了内部语言行为。它们可以使用以下属性访问:
Symbol.hasInstance一个在执行instanceof时调用的内部方法,用于检测对象的继承信息。Symbol.isConcatSpreadable一个布尔值,用于表示当传递一个集合作为Array.prototype.concat方法参数时,是否应该将集合内的元素规整到同一层级Symbol.iterator一个返回迭代器的方法Symbol.match一个在调用String.prototype.match方法时调用的方法,用于比较字符串。Symbol.replace一个在调用String.prototype.replace方法时调用的方法,用于替换字符串的子串。Symbol.search一个在调用String.prototype.search方法时调用的方法,用于在字符串中定位子串。Symbol.species用于创建派生对象的构造函数。Symbol.split一个在调用String.prototype.split方法时调用的方法,用于分割字符串。Symbol.toPrimitive一个返回对象原始值的方法。Symbol.toStringTag一个在调用String.prototype.toString方法时调用的方法,用于创建对象描述。Symbol.unscopables一个定义了一些不可被with语句引用的对象属性名称的对象集合。
Symbol.hasInstance
每一个函数都有一个Symbol.hasInstance方法,用于确定对象是否为函数的实例。该方法在Function.prototype中定义,所有所有函数都继承了instanceof属性的默认行为。为了确保Symbol.hasInstance不会被意外重写,该方法被定义为不可写、不可配置且不可枚举。
obj instantce Array等价于Array[Symbol.hasInstance](obj)
所以,ES6 只是将instanceof操作符重新定义为此方法的简写语法,现在我们可以随意地改变instanceof的运行方式了。比如:
function MyObject() {}
Object.defineProperty(MyObject, Symbol.hasInstance, {
value: function(v) {
return false
}
})
const obj = new Object()
console.log(obj instanceof MyObject) // false
只有通过
Object.defineProperty()方法才能改写一个不可写属性。
Symbol.isConcatSpreadable
Symbol.isConcatSpreadable是一个布尔值,如果该属性为true,则表示对象有length属性和数字键,故它的数值属性应该被独立添加到concat调用结果中。
const collection = {
0: 'hello',
1: 'world',
length: 2,
[Symbol.isConcatSpreadable]: true
}
const result = ['hi'].concat(collection)
console.log(result.length) // 3
console.log(result)
也可以在派生类数组子类中将
Symbol.isConcatSpreadable设置为false,从而防止元素在调用concat方法时被分解。
Symbol.toPrimitive
在JS中,当执行特定操作时,有时尝试将对象转换到相应的原始值。在ES6中,通过Symbol.toPrimitive方法就可以更改那个暴露出来的值。调用Symbol.toPrimitive会传入一个值作为参数(hint),有如下三个值:
- number
- string
- default
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
console.log(2 * obj) // 246
console.log(3 + obj) // '3default'
console.log(obj == 'default') // true
console.log(String(obj)) // 'str'
Symbol.toStringTag
ES6 通过 Symbol.toStringTag改变了调用Object.prototype.toString()时返回的身份标识。
也就是说,这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。
function Person(name){
this.name = name
}
Person.prototype[Symbol.toStringTag] = 'Person'
const p1 = new Person('Jack')
console.log(p1.toString()) // [object Person]
console.log(Object.prototype.toString.call(p1)) // [object Person]