【ES6 笔记】Symbol和Symbol属性

2018-11-01  本文已影响0人  ___Jing___

回忆一下JS中的原始类型:字符串型、数字型、布尔型、null和undefined。

ES6中引入了第6种原始类型:Symbol

创建Symbol

let firstName = Symbol();
let person = {};
person[firstName] = '欧阳不乖'
console.log(person[firstName]); //'欧阳不乖'

Symbol函数接受一个可选参数,可以添加一段文本描述即将创建的Symbol,这段属描述不可用于属性访问,但是建议每次创建Symbol时都添加一段描述,便于阅读代码和调试Symbol程序。

let firstName = Symbol('first name');
let person = {};
person[firstName] = '欧阳不乖';
console.log('first name' in person); //false
console.log(person[firstName]); // ''欧阳不乖
console.log(firstName); //Symbol('first name')

Symbol的描述被存储在内部的[[Description]]属性中,只有调用Symbol的toString()方法时才可以读取这个属性。在执行console.log的时候隐式的调用了toString()方法。

let symbol = Symbol('test symbol');
 console.log(typeof symbol);  //'symnbol'      

Symbol的使用方法

所有使用可计算属性名的地方,都可以使用Symbol。

let firstName = Symbol('first name');
let person = {
    //使用一个可计算对象字面量属性
    [firstName] : '欧阳不乖'
}
//将属性设置为只读
Object.defineProperty( person, firstName, { writable : false});
console.log(person[firstName]); //'欧阳不乖'

Symbol共享体系

如果想创建一个可共享的Symbol,要使用Symbol.for()方法。它只接受一个参数,也就是即将创建的Symbol的字符串标识符,这个参数同样也被用作Symbol的描述:

let uid = Symbol.for('uid');
let object = {};
object[ uid ] = '12345';
console.log(object[uid]); //12345
console.log(uid); //Symbol(uid)

Symbol.for()方法首先在全局Symbol注册表中搜索键为‘uid’的Symbol是否存在,如果存在,直接返回已有的Symbol;否则,创建一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随机返回新创建的Symbol。
后续如果再传入同样的键调用Symbol.for()会返回相同的Symbol:

let uid = Symbol.for('uid');
let uid2 = Symbol.for('uid');
let object = {
     [uid] : '12345' 
} ;
console.log(uid === uid2); //true
console.log(object[uid]);  //12345
console.log(object[uid2]);  //12345

还有一个与Symbol共享有关的特性:可以使用Symbol.keyFor()方法在Symbol全局注册表中检索与Symbol有关的键:

let uid = Symbol.for('uid');
console.log(Symbol.keyFor(uid)); //uid

let uid2 = Symbol.for('uid');
console.log(Symbol.keyFor(uid2)); //uid

let uid3 = Symbol('uid');
console.log(Symbol.keyFor(uid3)); //undefined

Symbol全局注册表是一个类似全局作用域的共享环境,也就是说你不能假设目前环境中存在哪些键

Symbol与类型强制转换

由于其他类型没有与Symbol逻辑等价的值,所以不能将Symbol强制转换为字符串或是数字类型。
在使用console.log()方法来输出Symbol的内容时,它会调用Symbol的String()方法并输出有用的信息。也可以像下面这样直接调用String()方法来获取相同的内容:

let uid = Symbol.for('uid'),
    desc = String(uid);
console.log(desc); //Symbol(uid)

String()函数调用了uid.toString()方法,返回字符串类型的Symbol描述内容,但是,如果将Symbol与一个字符串拼接会导致程序抛出错误:

let uidDesc = Symbol.for('uid') + ''; //报错

Symbol不可以被转为字符串,同样也不能转为数字类型:

let uidSum = Symbol.for('uid') /1; //报错

只有在使用逻辑操作符的时候,Symbol可以正常运行,因为Symbol与JS中的非空值类似,其等价布尔值为true

Symbol属性检索

Object.keys()和Object.getOwnPropertyNames()方法可以检索对象中所有的属性名:前一个方法返回所有的可枚举属性名;后一个方法不考虑属性的可枚举性一律返回。在ES6中新增一个Object.getOwnPropertySymbols()方法来检索对象中的Symbol属性。
Object.getOwnPropertySymbols()方法的返回值是一个包含所有Symbol自有属性的数组:

let uid = Symbol.for('uid');
let object = {
     [uid]:12345
}
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length); //1
console.log(symbols[0]);  //Symbol('uid')
console.log(object[symbols[0]]);   //12345

通过well-known Symbol暴露内部操作

let arr = []
console.log(arr instanceof Array);  //true
// 等价于
console.log(Array[Symbol.hasInstance](arr));  //true

本质上,ES6只是将instanceof操作符重新定义为此方法的简写语法,现在引入方法调用以后,就可以随便改变instanceof的运行方式了:

function MyObject(){
    // empty
}
Object.defineProperty(MyObject, Symbol.hasInstance,{
    value : function(v){
          //console.log(v);   //MyObject {}
          return false;
    }
})
let obj = new MyObject();
console.log(obj instanceof MyObject );  // false
/*
* obj实际上是MyObject的实例,但是我们将Symbol.hasInstance的返回值硬编码为false以后
* 即使使用instanceof运算符也只是返回false
*/

我们可以按照自己的喜好任意重构Symbol.hasInstance,但是改写源码会造成不可预期的后果,所以请在必要的情况下只改写自己声明的函数Symbol.hasInstance属性

let colors1 = ['red'];
let colors2 = ['green'];
let color3 = 'black';
console.log(colors1.concat(colors2, color3)); //["red", "green", "black"]

JS规范声明,凡是传入了数组的参数,就会自动将他们分解为独立元素。
Symbol.isConcatSpreadable属性是一个布尔值,如果该属性值为true,则表示对象有length属性和数字键,故它的数值型属性值应该被独立添加到concat调用的结果中。这个属性默认情况下不会出现在标准对象中,它只是可选属性,用于增强作用于特定对象类型的concat方法的功能,有效简化其默认特性:

let collection = {
      0:'Hello',
      1:'World',
      length:2,
      [Symbol.isConcatSpreadable]:true
}
let message = ['Hi'].concat(collection);
console.log(message.length); //3
console.log(message); //["Hi", "Hello", "World"]

// 假设将 [Symbol.isConcatSpreadable]:false改为这样,那么运行结果就变为了:
/ * 
* console.log(message);
* ['Hi', {
*       0:'Hello',
*       1:'World',
*       length:2,
*       [Symbol.isConcatSpreadable]:true
* }]
* /
// 实际上等价于 /^.{10}$/
let hasLengthOf10 = {
    [Symbol.match]:function(value){
        return value.length ===10 ? [ value ] : null ;
    },
    [Symbol.replace]:function(value, replacement){
        return value.length ===10 ? replacement : value ;
    },
    [Symbol.search]:function(value){
        return value.length ===10 ? 0 : -1 ;
    },
    [Symbol.split]:function(value){
        return value.length ===10 ? [ ,  ] : [ value ] ;
    },  
};
let message1 = 'Hello world'; //11个字符
let message2 = 'Hello 1234'; //10个字符

console.log( message1.match(hasLengthOf10) ); //null
console.log( message2.match(hasLengthOf10) ); //["Hello 1234"]

console.log( message1.replace(hasLengthOf10,'欧阳不乖') ); //Hello world
console.log( message2.replace(hasLengthOf10,'欧阳不乖') ); //欧阳不乖

console.log( message1.search(hasLengthOf10) ); // -1
console.log( message2.search(hasLengthOf10) ); // 0

console.log( message1.split(hasLengthOf10) ); //["Hello world"]
console.log( message2.split(hasLengthOf10) ); // [ ,  ] 注意这里原书写的['','']个人认为二者有区别
function Temperature(degrees){
    this.degrees = degrees;
}
Temperature.prototype[Symbol.toPrimitive] = function(hint){
    switch (hint){
        case 'string' : 
                return this.degrees + '\u00b0' ; //degrees symbol
        case 'number':
                return this.degrees;
        case 'default':
                return this.degrees + '度'
    }
}
var freezing = new Temperature(32);

console.log( freezing/2 ) //16
console.log(String(freezing) ) //32°
console.log( freezing + '!' );  //32度!

Symbol 的一些小扩展

let firstName = Symbol('欧阳不乖');
let lastName  ='Hello';
let person = {
    [firstName]:'爱谁谁',
    [lastName]:'World'
}

console.log( person.firstName ); // undefined
console.log( person[Symbol('欧阳不乖')] ) //undefined
console.log( person[firstName] ); // 爱谁谁

console.log( person.last ); // undefined
console.log( person[lastName] ); // World

console.log( firstName ); // Symbol('欧阳不乖')
console.log( Symbol('欧阳不乖') ); // Symbol('欧阳不乖')
console.log( firstName==Symbol('欧阳不乖') ); // false

console.log( String(Symbol('欧阳不乖'))+'yes' ); //Symbol(欧阳不乖)yes

console.log( Symbol('欧阳不乖')=== Symbol('欧阳不乖')  ); //false
console.log( Symbol('欧阳不乖')== Symbol('欧阳不乖')  ); //false

console.log( Object.is( Symbol('欧阳不乖'), Symbol('欧阳不乖')) );  //false

上一篇 下一篇

猜你喜欢

热点阅读