一、基础知识
Symbol
是JavaScript的第7种数据类型,用来表示具有唯一性的值,创建一个symbol数据类型的变量,要通过Symbol()
函数,如:
let s = Symbol();
typeof s; // 'symbol'
可以给Symbol()
函数传递一个参数,这个参数是个字符串,表示对Symbol对象的描述,如:
let s = Symbol('a symbol variable');
s; // Symbol(a symbol variable)
需要注意的是,每次使用Symbol()
创建的变量,都是不同的,即使它们的描述相同:
let s1 = Symbol();
let s2 = Symbol();
s1 === s2; // false
即使描述相同,它们也是两个不同的symbol变量
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2; // false
symbol值不能与其他的数据类型进行运算,否则会报错,如以下的运算都是不允许的:
s + '';
s + 1;
s + true;
但是symbol值可以 显式转化 为字符串,如:
let s = Symbol('desc');
String(s); // 'Symbol(desc)'
symbol值也可以转为boolean值,但是不能转为数值,如:
let s = Symbol('desc');
Boolean(s); // true
Number(s); // 报错
二、作为属性名
由于每个symbol值都是唯一的、不相等的,所以我们可以使用symbol
类型的值来作为标识符,如作为属性的名,这样子就能够避免值被覆盖或改写,如:
const symbolKey = Symbol();
const obj = {
[symbolKey]: 'Hello, world'
}
需要注意的是:在获取对象的属性的时候,不能使用.
语法,如果使用.
的话,就会被认为是一个string
类型的key,而不是symbol类型的key,正确的方法是使用方括号[]
由于Symbol
的这些特性,我们可以很方便的用它来定义一组常量,如:
const COLOR = {
RED: Symbol('RED'),
BLUE: Symbol('BLUE'),
BLACK: Symbol('BLACK')
};
三、属性名的遍历
如果我们使用for-in
、Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
这些方法的话,symbol类型的key是不会被遍历进去的,如果我们想要遍历来得到symbol类型的属性,那么可以使用Object.getOwnPropertySymbols()
,如:
const obj = {
a: 'A',
b: 'B',
[Symbol()]: 'symbol'
};
Object.defineProperty(obj, 'unenumerable', {
value: '123',
enumerable: false
});
Object.keys(obj); // ['a', 'b']
Object.getOwnPropertyNames(obj); // ['a', 'b']
Object.getOwnPropertySymbols(obj); // [Symbol()]
此外,可以使用新的API:Reflect.ownKeys()
来得到一个对象下所有的可枚举的、不可枚举的,包括symbol类型在内的key:
Reflect.ownKeys(obj); // ['a', 'b', 'unenumerable', Symbol()]
四、Symbol.for()
、Symbol.keyFor()
有时候,我们希望重新使用一个Symbol值,那么这种情况下,就可以使用Symbol.for()
,这个函数接收一个string类型的参数,表明是以此为名称的Symbol值,和Symbol()
函数的区别是:
每次调用Symbol()
都会生成一个新的symbol值,而调用Symbol.for(sKey)
则会先在全局登记环境里查找是否有名称为sKey
的symbol值,如果有的话,直接返回,没有的话先新建一个,然后再返回,如:
const s1 = Symbol();
const s2 = Symbol();
s1 === s2; // false;
const s1 = Symbol.for('foo');
const s2 = Symbol.for('foo');
s1 === s2; // true;
所以我们Symbol.for()
创建的symbol值是带登记的symbol值,带登记的symbol值可以使用Symbol.keyFor()
来获取它的名称:
const s1 = Symbol.for('HelloWorld');
Symbol.keyFor(s1); // 'HelloWorld'
而不带登记的symbol值则不行,如:
const s = Symbol();
Symbol.keyFor(s); // undefined
五、内置的Symbol值
ES6内置了11个内置的Symbol值,指向语言内部使用的方法
1、Symbol.hasInstance
当调用instanceof
运算符时,会调用右操作符的Symbol.hasInstance
方法,如:
const obj = {
[Symbol.hasInstance]() {
return true;
}
}
const a = {};
a instanceof obj; // true
2、Symbol.isConcatSpreadable
表示对象用于Array.prototype.concat()
时是否可展开,Symbol.isConcatSpreadable
是一个布尔值,当设为true
时,表示可展开。默认情况下这个值为undefined
(并不表示默认可否展开),但是当我们期望它展开的时候,可以将它设为true
:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = {0: 7, 1: 8, 2: 9, length: 3};
arr1.concat(arr2); // [1, 2, 3, 4, 5, 6]
arr1.concat(arr3); // [1, 2, 3, {0: 7, 1: 8, 2: 9, length: 3}]
arr3[Symbol.isConcatSpreadable] = true;
arr1.concat[arr3]; // [1, 2, 3, 7, 8, 9]
3、Symbol.species
创造实例时,会调用Symbol.species
方法,用它返回的函数作为构造函数,来创造新的实例对象,如:
class MyArray extends Array {
static get [Symbol.species]() {
return Array;
}
}
const a = new MyArray(1, 2, 3);
const m = a.map(x => x*x);
m instanceof MyArray; // false
m instanceof Array; // true
4、Symbol.match
当一个对象obj
的Symbol.match()
方法存在时,调用str.match(obj)
时就会调用[Symbol.match]()
,其返回值作为str.match(obj)
的返回值,如:
const isHello = {
[Symbol.match](str) {
return str === 'Hello';
}
}
'Hello'.match(isHello); // true
'HelloWorld'.match(isHello); // false
5、Symbol.replace
调用String.prototype.replace
方法中时会调用[Symbol.replace]()
,其中:
- 第一个参数为调用
replace()
方法的字符串 - 第二个参数为传给
replace()
方法的第二个参数
如:
const obj = {};
obj[Symbol.replace] = (str, toReplaceWith) => {
return str + toReplaceWith;
}
'Hello'.replace(obj, ' World'); // Hello World
6、Symbol.search
当调用String.prototype.search()
时,如果obj的[Symbol.search]()
存在,那么就会调用[Symbol.search]()
并将其返回值作为str.search(obj)
的返回值:
const obj = {
[Symbol.search]() {
return 'Hello';
}
}
'str'.search(obj); // 'Hello'
7、Symbol.split
当调用String.prototype.split()
时,如果obj的[Symbol.split]()
存在,那么就会调用[Symbol.split]()
并将其返回值作为str.split(obj)
的返回值:
const getEachChar = {
[Symbol.split](str) {
return str.split('');
}
}
'abcd'.split(getEachChar); // ['a', 'b', 'c', 'd']
8、Symbol.iterator
指定对象迭代时所调用的方法,如使用for-of
循环或者...
时,会调用Symbol.iterator
方法
9、Symbol.toPrimitive
当对象被转为原始值时,会调用[Symbol.toPrimitive]()
方法,以其返回值作为值。而这个方法调用的时候,会得到一个参数,表示当前的运算模式,有:
- number:该场合需要转为数值
- string:该场合需要转成字符串
- default:该场合可以转成数值,也可以转成字符串
let obj = {
[Symbol.toPrimitive](hint) {
switch (hint) {
case 'number': return 10;
case 'string': return 'World';
case 'default': return '[Default]';
default: throw new Error();
}
}
}
2 + obj; // '2[Default]'
2 * obj; // 20
String(obj); // 'World'
10、Symbol.toStringTag
用来指定Object.prototype.toString.call()
返回的[Object xxx]
中xxx
的值,如:
const obj = {}
obj[Symbol.toStringTag] = 'DIY';
Object.prototype.toString.call(obj); // [object DIY]
ES6里各个内置对象的Symbol.toStringTag
的值如下
JSON
、Math
、ArrayBuffer
、DataView
、Map
、Promise
、Set
、WeapMap
、WeakSet
、Symbol
、Generator
、GeneratorFunction
:和构造函数的名称一样%TypedArray%
:Uint8Array等%MapIteratorPrototype%
:Map Iterator%SetIteratorPrototype%
:Map Iterator%StringIteratorPrototype%
:Map Iterator
11、Symbol.unscopables
指定当该对象使用了with
时,有哪些属性会被with
环境排除,如:
const obj = {
foo() {
console.log('Inner');
}
}
function foo() {
console.log('Outer');
}
with(obj) {
foo(); // Inner
}
// 指定后
obj[Symbol.unscopables] = { foo: true }
with(obj) {
foo(); // Outer
}