Symbol的学习
概述
Symbol是一种新的原始数据类型,是es6的第七种数据类型,其余六种分别是:undefined,null,布尔值(Boolean),字符串(String),数值(Number),对象(Object)。
通过Symbol函数创建一个Symbol,对象的属性名有两种类型:字符串和Symbol.Symbol 的属性名是独一无二的,不会与任何属性名冲突。
声明Symbol变量
let s = Symbol()
typeof s//"symbol"
特点
- 不能使用new创建,因为它是原始数据类型,而不是对象或者数组
- 为了区分,可以接受一个字符串的参数
let s1 =Symbol('foo')
let s2= Symbol('boo')
s1//Symbol(foo)
s2//Symbol(boo)
s1.toString()//"Symbol(foo)"
s2.toString()//"Symbol(boo)"
- 如果参数是对象的话,需要将参数转换成字符串
let obj={
toString(){
return "abc"
}
}
let s =Symbol(obj)
s//Symbol(abc)
- Symbol是独一无二的,因此无参和同参的函数返回都是不相等
//无参
let s1 = Symbol();
let s2 = Symbol();
s1 === s2;//false
//有参
let s3 =Symbol("foo");
let s4 =Symbol("foo");
s3 === s4;//false
- 不能与其他类型进行运算
let sym =Symbol("My Symbol");
" my symbol is"+ sym;
//TypeError: Cannot convert a Symbol value to a string
'my symbol is $(sym)'
//TypeError: Cannot convert a Symbol value to a string
- 可以直接转换成字符串和布尔值,但是不能转换成数值
let sym = Symbol("My Symbol")
String(sym)//"Symbol(My Symbol)"
sym.toString()//"Symbol(My Symbol)"
Boolean(sym)//true
!sym//false
Number(sym)//TypeError: Cannot convert a Symbol value to a number
sym+2 //TypeError: Cannot convert a Symbol value to a number
作为属性名的Symbol
- 作为对象的属性名,保证不会出现相同的标识符
let mySymbol = Symbol();
//第一种写法
let a ={};
a = {
[mySymbol]:"hello"
}
//第二种
let a={
[mySymbol]:"hello"
}
//第三种
let a ={};
Object.defineProperty(a,mySymbol,{value:'hello'})
//{ [Symbol()]: 'Hello!' }
a[mySymbol]//"hello"
- 不能使用点运算符,因为点运算符后面跟着的是字符串,不能识别为Symbol值
const mySymbol = Symbol();
const a ={};
a.mySymbol="hello";
a[mySymbol]//undefined
a['mySymbol']//"hello"
- 如果在对象内部,必须使用方括号,否则键名将会变成字符串,而不是Symbol值
let mySymbol = Symbol();
let obj ={
[mySymbol](arg)
}
obj[mySymbol](123)
- 定义一组互不相等的常量,常用于对象属性值和switch语句
const log ={};
log.levels={
DEBUG:Symbol('debug'),
INFO:Symbol('info').
WARN:Symbol('warn')
}
//{ DEBUG: Symbol(debug), INFO: Symbol(info), WARN: Symbol(warn) }
console.log(log.levels.DEBUG,'debug message')
//Symbol(debug) 'debug message'
const RED =Symbol()
const GREEN = Symbol()
function getComplement(color){
switch(color){
case RED:
return GREEN;
break;
case GREEN:
return RED;
break;
default:throw new Error("undefined color");
}
}
实例:消除魔术字符串
魔术字符串就是代码中重复多次的,与代码形成强耦合关系的某一具体字符串,良好的代码风格,就是要尽量避免魔术字符串的出现
function getArea(shape,options){
switch(shape){
case 'triangle':
area = .5 *options.width*options.height;
break;
}
return area;
}
getArea('trangle',{width:100,height:500})//魔术字符串为triangle
//250000
消除方法
- 可以将triangle变成一个变量
- 写成Symbol值
const shapeType ={
triangle:'triangle'
//triangle:Symbol
};
function getArea(shape,options){
switch(shape){
case shapeType.triangle:
area = .5* options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle,{width:100,height:100});
//5000
有利于代码的修改和维护
属性名的遍历
在Symbol作为属性名遍历的时候,不会for ... in 或者for...of循环以及Object.keys(),Object.getOwnPropertyName(),JSON.stringify()等方法获取。
一般使用getOwnPropertySymbols()和Reflect.ownKeys()
- Object.getOwnpropertySymbols()的参数是一个对象,返回是一个数组,成员是当前对象所有属性名的Symbol值
const obj = {};
const a= Symbol('a');
const b=Symbol('b');
obj[a] = 'hello';//"hello"
obj[b] ='world'; //"world"
let propertySymbol =Object.getPropertySymbol(obj);
[Symbol(a),Symbol(b)]
- 与for ...in和Object.getOwnAPropertyNames()比较
let obj ={};
let mySymbol =Symbol('foo');
Object.getProperty(obj,mySymbol,{value:'hello!'});
//{[Symbol(foo)]:'hello!'}
for( let i in obj){
console.log(i)//无输出
}
Oject.getOwnPropertyNanmes(obj);//[]
Oject.getOwnPropertySymbols(obj);//[Symbol(foo)]
- Reflect.ownKeys()获取所有包括常规和Symbol的属性名.返回一个数组.成员当前对象为所有属性名
let mySymbol=Symbol();
let obj1 ={
[mySymbol]:'num1',
num1:1,
flag:true
};
Reflect.ownKeys(obj1);//[ 'num1', 'flag', Symbol() ]
Symbol.for()和Symbol.keyFor()
- Symbol.for接受一个字符串参数作为键名,在全局环境中进行查找,如果已经存在,直接返回值,如果不存在,则新建,但是Symbol()不管存在与否都会新建,
let sym1 = Symbol.for('foo');
let sym2 =Symbol.for('foo');
sym1===sym2;//true;
let sym3=Symbol('foo');
sym3=sym1;//false
- Symbol.keyFor返回一个已经登记的Symbol的key
let s1=Symbol.for('foo');
Symbol.keyFor(s1);//"foo"
let s2=Symbol();
Symbol.keyFor(s2);//undefined
内置的Symbol值
Symbol.hasInstance()
指向一个内部方法,当其他对象使用instanceOf时,判断是否为该对象的实例时调用
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
[1, 2, 3] instanceof new MyClass() // true
Symbol.isConcatSpreadable
对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开
class A1 extends Array {
constructor(args) {
super(args);
this[Symbol.isConcatSpreadable] = true;
}
}
class A2 extends Array {
constructor(args) {
super(args);
}
get [Symbol.isConcatSpreadable] () {
return false;
}
}
let a1 = new A1();
a1[0] = 3;
a1[1] = 4;
let a2 = new A2();
a2[0] = 5;
a2[1] = 6;
[1, 2].concat(a1).concat(a2)
// [1, 2, 3, 4, [5, 6]]
Symbol.species
对象的Symbol.species属性,指向一个构造函数。创建衍生对象时,会使用该属性
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray();
const b = a.map(x => x);
b instanceof MyArray // false
b instanceof Array // true
Symbol.match
对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果该属性存在,会调用它,返回该方法的返回值。
String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)
class MyMatcher {
[Symbol.match](string) {
return 'hello world'.indexOf(string);
}
}
'e'.match(new MyMatcher()) // 1
Symbol.repalce
对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。
const x = {};
x[Symbol.replace] = (...s) => console.log(s);
'Hello'.replace(x, 'World') // ["Hello", "World"]
Symbol.search
对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)
class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
'foobar'.search(new MySearch('foo')) //
Symbol.split
对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
let index = string.indexOf(this.value);
if (index === -1) {
return string;
}
return [
string.substr(0, index),
string.substr(index + this.value.length)
];
}
}
'foobar'.split(new MySplitter('foo'))
// ['', 'bar']
'foobar'.split(new MySplitter('bar'))
// ['foo', '']
'foobar'.split(new MySplitter('baz'))
// 'foobar'
Symbol.iterator
对象的Symbol.iterator属性,指向该对象的默认遍历器方法
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
Symbol.toPrimitive
Symbol.toPrimitive被调用时,会接受一个字符串参数,表示当前运算的模式,一共有三种模式。
- 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();
}
}
};
2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
Symbol.toStringTag
对象的Symbol.toStringTag属性,指向一个方法。
({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"
// 例二
class Collection {
get [Symbol.toStringTag]() {
return 'xxx';
}
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"
Symbol.unscopables
对象的Symbol.unscopables属性,指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。
Array.prototype[Symbol.unscopables]
// {
// copyWithin: true,
// entries: true,
// fill: true,
// find: true,
// findIndex: true,
// includes: true,
// keys: true
// }
Object.keys(Array.prototype[Symbol.unscopables])
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']