ECMAScript.6 Symbol相关
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如使用了一个他人提供的对象,但又想为这个对象添加新的属性或方法,如果开发者这对当前对象结构不太了解,这种操作有很大的可能会覆盖原来对象的属性或方法,由于Symbol是独一无二的,使用Symbol属性可以保证不会与其他属性名产生冲突。
创建Symbol
// 创建Symbol
let firstName = Symbol();
let person = {};
person[firstName] = 'Nicholas';
console.log(person[firstName]); //'Nicholas'
ps:1.在命名symbol变量,尽量确定该变量名代表的含义,以防后期变量过多,容易混淆。
2.Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型
Symbol函数可以接受一个可选的参数用来描述Symbol的实例。
let firstName = Symbol('first name');
let person = {};
person[firstName] = 'Nicholas';
console.log('first name' in person); // false
console.log(person[firstName]); //'Nicholas'
console.log(firstName);// "Symbol(first name)"
通过以下方法检测变量是否是Symbol类型
let symbol = Symbol('test symbol');
console.log(typeof symbol); // "symbol"
使用Symbol
let firstName = Symbol('first name');
// 对象字面量中 使用 [] 声明Symbol属性
let person = {
[firstName]:'Nicholas'
}
// 设置对象一个属性属性
Object.defineProperty(person,firstName,{writable:false});
let lastName = Symbol('last name');
// 设置对象多个属性值
Object.defineProperties(person,{
[lastName]:{
value:'Zakas',
writable:false
}
});
console.log(person[firstName]); // "Nicholas"
console.log(person[lastName]); // "Zakas"
共享Symbol
有些情况我们希望使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。
// Symbol.for
let uid= Symbol.for("uid");
let object = {};
object[uid] = "12345";
console.log(object[uid]); // "12345"
console.log(uid); //"Symbol(uid)"
let uid= Symbol.for('uid');
let object = {};
object[uid] = "12345";
console.log(object[uid]); // "12345"
console.log(uid); //"Symbol(uid)"
let uid2 = Symbol.for('uid');
console.log(uid2 === uid); // true
console.log(object[uid2]); // "12345"
console.log(uid2); //"Symbol(uid)"
let uid3 = Symbol('uid');
let uid4 = Symbol('uid');
console.log(uid3 == uid4); // false
console.log(uid3 === uid4); // false
从上述代码可以看出使用Symbol.for生成的变量uid及uid2都指向的相同的Symbol实例,但对于Symbol所生成的uid3及uid4则是不同的。
Symbol.keyFor方法返回一个已登记的(即通过Symbol.for生成过的) Symbol 类型值的key。
// Symbol.keyFor
let symbol1 = Symbol.for('uid');
console.log(Symbol.keyFor(symbol1)); // "uid"
let symbol2 = Symbol.for('uid');
console.log(Symbol.keyFor(symbol2)); // "uid"
let symbol3 = Symbol('uid');
console.log(Symbol.keyFor(symbol3)); // undefined
Symbol类型转换
let uid = Symbol.for('uid');
console.log(uid.toString()); // "Symbol(uid)"
console.log(String(uid)); // "Symbol(uid)"
上述代码使用Symbol实例的toString方法,返回关于uid的描述。
var uid = Symbol.for('uid');
let desc = uid + ''; //error
上述代码希望通过 “+ ‘‘” 将Symbol实例转换成String’类型,但这个不可以的,因为Symbol不支持强制类型转换,同理如下面代码
var uid = Symbol.for('uid');
let num = uid / 1; //error
获取对象Symbol属性
es5的Object.keys方法返回一个包含该对象所有可枚举的字符串数组。Object.getOwnPropertyNames方法该对象所有实例属性无论属性是否枚举,但这两个方法都不返回Symbol属性。es6增加getOwnPropertySymbols返回该对象Symbol属性的数组。
let uid = Symbol.for('uid');
let object = {
[uid]:'12345',
name:'hh'
}
console.log(Object.keys(object)); // ["name"]
console.log(Object.getOwnPropertyNames(object)); // ["name"]
console.log(Object.getOwnPropertySymbols(object)); //[Symbol(uid)]
内置的Symbol值
ES6还提供了11个内置的Symbol值,指向语言内部使用的方法。
Symbol.hasInstance
对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。
// eg1
function MyObject1(){}
var obj11 = {};
var obj12 = new MyObject1();
console.log(obj11 instanceof MyObject1); //false
console.log(obj12 instanceof MyObject1); //true
// eg2
function MyObject2(){}
Object.defineProperty(MyObject2,Symbol.hasInstance,{
value:function(v){
return false;
}
});
var obj21 = {};
var obj22 = new MyObject2();
console.log(obj21 instanceof MyObject2);// false
console.log(obj22 instanceof MyObject2);// false 上述两个例子eg1使用原生instanceof运算符判断对象是否是MyObject1的实例,obj11是空对象故返回false,obj12为 new MyObject1() 生成的的实例故返回true。 eg2中使用instanceof运算符会自动调用Symbol.hasInstance方法,即使obj22理论上为MyObject2的实例但依旧会返回false。
Symbol.isConcatSpreadable
对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象使用Array.prototype.concat()时,是否可以展开。
let colors1 = ['red','green'];
let colors2 = ['blue','black'];
console.log(colors1.concat(colors2,'brown')); // ["red", "green", "blue", "black", "brown"]
colors2[Symbol.isConcatSpreadable] = false;
console.log(colors1.concat(colors2,'brown')); // ["red", "green", ["blue", "black"], "brown"] 上述代码中colors2数组设置Symbol.isConcatSpreadable为false导致数组不进行展开合并。
let collection = {
0: "Hello",
1: "world",
length: 2,
[Symbol.isConcatSpreadable]: true
};
let messages = [ "Hi" ].concat(collection);
console.log(messages.length); // 3
console.log(messages); // ["hi","Hello","world"] 上述代码中concat可以合并类数组的对象。
Symbol.match、Symbol.replace、Symbol.search、Symbol.split
Symbol.match
对象的Symbol.match属性,指向一个函数。当执行str.match(myObject)时,如果myObject存在Symbol.match属性,会调用该属性对应的方法。
// Symbol.match
let RegObj = {
[Symbol.match](string){
return 'hello world'.indexOf(string);
}
}
'e'.match(RegObj); // 1
Symbol.replace
对象的Symbol.replace属性,指向一个函数。当执行str.replace(myObject)时,如果myObject存在Symbol.replace属性,会调用该属性对应的方法。
// Symbol.replace
let RegObj = {
[Symbol.replace](...s){
console.log(s);
}
}
'hello'.replace(RegObj,'world'); //["hello", "world"]
Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,上面例子是hello,第二个参数是替换后的值,上面例子是world。
Symbol.search
对象的Symbol.search属性,指向一个函数,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。
// Symbol.search
let RegObj = {
[Symbol.search](value){
return value.length === 10 ? 0 : -1;
}
}
let message1 = "Hello world"; // 11 characters
let message2 = "Hello John"; // 10 characters
let search1 = message1.search(RegObj);
let search2 = message2.search(RegObj);
console.log(search1); // -1
console.log(search2); // 0
Symbol.split
对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.split方法调用时,会返回该方法的返回值。
let RegObj = {
[Symbol.split](value, spliter) {
var index = value.indexOf(spliter);
if (index === -1) {
return value;
}
return [
value.substr(0, index),
value.substr(index + spliter.length)
];
}
}
console.log('foobar'.split(RegObj, 'foo')); //["", "bar"]
console.log('foobar'.split(RegObj, 'bar')); //["foo", ""]
console.log('foobar'.split(RegObj, 'baz')); //["", ""]
Symbol.split 方法中第一个参数为待分割的字符串,第二个参数为分割标识符。
Symbol.toPrimitive
对象的Symbol.toPrimitive属性,指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。 Symbol.toPrimitive被调用时会根据当前场合接受一个字符串参数,该参数表示一种运算模式,传入到对应方法并返回相应的值。
- number:该场合需要转成数值
- string:该场合需要转成字符串
- default:该场合可以转成数值,也可以转成字符串
function Temperature(degrees){
this.degrees = degrees;
}
Temperature.prototype[Symbol.toPrimitive] = function(hint){
switch(hint){
case 'string':
return this.degrees + '\u00b0';
case 'number':
return this.degrees;
case 'default':
return this.degrees + ' degrees';
}
}
var freezing = new Temperature(32);
console.log(freezing/2); // 16
console.log(String(freezing)); // 32°
console.log(freezing + '!'); //32 degrees!
Symbol.toStringTag
这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串
function Person(name){
this.name = name;
}
var per1 = new Person('Nicholas');
console.log(per1.toString()); // "[object Object]"
Person.prototype[Symbol.toStringTag] = "Person";
var per2 = new Person('Nicholas');
console.log(per2.toString()); // "[object Person]"