回憶一下JS中的原始類型:字符串型、數(shù)字型崩溪、布爾型、null和undefined。
ES6中引入了第6種原始類型:Symbol
創(chuàng)建Symbol
let firstName = Symbol();
let person = {};
person[firstName] = '歐陽不乖'
console.log(person[firstName]); //'歐陽不乖'
Symbol函數(shù)接受一個可選參數(shù)挺尾,可以添加一段文本描述即將創(chuàng)建的Symbol,這段屬描述不可用于屬性訪問站绪,但是建議每次創(chuàng)建Symbol時都添加一段描述遭铺,便于閱讀代碼和調(diào)試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的描述被存儲在內(nèi)部的[[Description]]屬性中恢准,只有調(diào)用Symbol的toString()方法時才可以讀取這個屬性魂挂。在執(zhí)行console.log的時候隱式的調(diào)用了toString()方法。
- Symbol的辨識方法
Symbol是原始值馁筐,且ES6同時擴展了typeof操作符涂召,支持返回“Symbol”,所以可以用typeof來檢測變量是否為Symbol類型
let symbol = Symbol('test symbol');
console.log(typeof symbol); //'symnbol'
Symbol的使用方法
所有使用可計算屬性名的地方敏沉,都可以使用Symbol果正。
let firstName = Symbol('first name');
let person = {
//使用一個可計算對象字面量屬性
[firstName] : '歐陽不乖'
}
//將屬性設(shè)置為只讀
Object.defineProperty( person, firstName, { writable : false});
console.log(person[firstName]); //'歐陽不乖'
Symbol共享體系
如果想創(chuàng)建一個可共享的Symbol,要使用Symbol.for()方法盟迟。它只接受一個參數(shù)秋泳,也就是即將創(chuàng)建的Symbol的字符串標(biāo)識符,這個參數(shù)同樣也被用作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;否則辖众,創(chuàng)建一個新的Symbol卓起,并使用這個鍵在Symbol全局注冊表中注冊,隨機返回新創(chuàng)建的Symbol凹炸。
后續(xù)如果再傳入同樣的鍵調(diào)用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共享有關(guān)的特性:可以使用Symbol.keyFor()方法在Symbol全局注冊表中檢索與Symbol有關(guān)的鍵:
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全局注冊表是一個類似全局作用域的共享環(huán)境戏阅,也就是說你不能假設(shè)目前環(huán)境中存在哪些鍵
Symbol與類型強制轉(zhuǎn)換
由于其他類型沒有與Symbol邏輯等價的值,所以不能將Symbol強制轉(zhuǎn)換為字符串或是數(shù)字類型还惠。
在使用console.log()方法來輸出Symbol的內(nèi)容時饲握,它會調(diào)用Symbol的String()方法并輸出有用的信息。也可以像下面這樣直接調(diào)用String()方法來獲取相同的內(nèi)容:
let uid = Symbol.for('uid'),
desc = String(uid);
console.log(desc); //Symbol(uid)
String()函數(shù)調(diào)用了uid.toString()方法蚕键,返回字符串類型的Symbol描述內(nèi)容救欧,但是,如果將Symbol與一個字符串拼接會導(dǎo)致程序拋出錯誤:
let uidDesc = Symbol.for('uid') + ''; //報錯
Symbol不可以被轉(zhuǎn)為字符串锣光,同樣也不能轉(zhuǎn)為數(shù)字類型:
let uidSum = Symbol.for('uid') /1; //報錯
只有在使用邏輯操作符的時候笆怠,Symbol可以正常運行,因為Symbol與JS中的非空值類似誊爹,其等價布爾值為true
Symbol屬性檢索
Object.keys()和Object.getOwnPropertyNames()方法可以檢索對象中所有的屬性名:前一個方法返回所有的可枚舉屬性名蹬刷;后一個方法不考慮屬性的可枚舉性一律返回瓢捉。在ES6中新增一個Object.getOwnPropertySymbols()方法來檢索對象中的Symbol屬性。
Object.getOwnPropertySymbols()方法的返回值是一個包含所有Symbol自有屬性的數(shù)組:
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暴露內(nèi)部操作
-
Symbol.hasInstance:一個在執(zhí)行instanceof時調(diào)用的內(nèi)部方法办成,用來檢測對象的繼承信息泡态。
Symbol.hasInstance方法只接受一個參數(shù),即要檢查的值迂卢。如果傳入的值是函數(shù)的實例某弦,則返回true:
let arr = []
console.log(arr instanceof Array); //true
// 等價于
console.log(Array[Symbol.hasInstance](arr)); //true
本質(zhì)上,ES6只是將instanceof操作符重新定義為此方法的簡寫語法而克,現(xiàn)在引入方法調(diào)用以后靶壮,就可以隨便改變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
*/
我們可以按照自己的喜好任意重構(gòu)Symbol.hasInstance员萍,但是改寫源碼會造成不可預(yù)期的后果腾降,所以請在必要的情況下只改寫自己聲明的函數(shù)Symbol.hasInstance屬性
-
Symbol.isConcatSpreadable:一個布爾值,用于表示當(dāng)傳遞一個集合作為Array.prototype.concat()方法的參數(shù)時碎绎,是否應(yīng)該將合集內(nèi)的元素規(guī)整到同一層級螃壤。
JS數(shù)組的concat方法被設(shè)計用于拼接兩個數(shù)組,不但接受數(shù)組參數(shù)筋帖,也可以接收非數(shù)組參數(shù):
let colors1 = ['red'];
let colors2 = ['green'];
let color3 = 'black';
console.log(colors1.concat(colors2, color3)); //["red", "green", "black"]
JS規(guī)范聲明映穗,凡是傳入了數(shù)組的參數(shù),就會自動將他們分解為獨立元素幕随。
Symbol.isConcatSpreadable屬性是一個布爾值,如果該屬性值為true宿接,則表示對象有l(wèi)ength屬性和數(shù)字鍵赘淮,故它的數(shù)值型屬性值應(yīng)該被獨立添加到concat調(diào)用的結(jié)果中。這個屬性默認情況下不會出現(xiàn)在標(biāo)準(zhǔn)對象中睦霎,它只是可選屬性梢卸,用于增強作用于特定對象類型的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"]
// 假設(shè)將 [Symbol.isConcatSpreadable]:false改為這樣副女,那么運行結(jié)果就變?yōu)榱耍?/ *
* console.log(message);
* ['Hi', {
* 0:'Hello',
* 1:'World',
* length:2,
* [Symbol.isConcatSpreadable]:true
* }]
* /
- Symbol.match:一個在調(diào)用String.prototype.match()方法時調(diào)用的方法蛤高,用于比較字符串。接受一個字符串類型的參數(shù)碑幅,如果匹配成功則返回匹配元素的數(shù)組戴陡,否則返回null。
- Symbol.replace:一個在調(diào)用String.prototype.replace()方法時調(diào)用的方法沟涨,用于替換字符串的子串恤批。接受一個字符串類型的參數(shù)和一個替換用的字符串,最終依然返回一個字符串裹赴。
- Symbol.search:一個在調(diào)用String.prototype.search()方法時調(diào)用的方法喜庞,用于在字符串中定位子串诀浪。接受一個字符串類型的參數(shù),如果匹配到則返回數(shù)字索引延都,否則返回 -1 雷猪。
-
Symbol.split:一個在調(diào)用String.prototype.split()方法時調(diào)用的方法,用于分割字符串晰房。接受一個字符串參數(shù)求摇,根據(jù)匹配內(nèi)容將字符串分解,并返回一個包含分解后片段的數(shù)組嫉你。
在JS中字符串與正則表達式經(jīng)常一起使用月帝,尤其是字符串類型的幾個方法,可以接受正則表達式作為參數(shù):
1. match(regex) 確定給定字符串是否匹配正則表達式regex
2. replace(regex幽污,replacement) 將字符串中匹配正則表達式的regex部分替換為replacement
3. search(regex) 在字符串中定位匹配正則表達式regex的位置索引
4. split(regex) 按照匹配正則表達式regex的元素將字符串分切嚷辅,并將結(jié)果存入數(shù)組
在ES6之前,以上4個方法無法使用開發(fā)者自定義的對象來替代正則表達式進行字符串匹配距误。而在ES6中定義了與上邊4個方法相對應(yīng)的4個Symbol簸搞,將語言內(nèi)建的RegExp對象的原生特性完全暴露出來。
// 實際上等價于 /^.{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) ); // [ , ] 注意這里原書寫的['','']個人認為二者有區(qū)別
-
Symbol.toPrimitive:該方法被定義在每一個標(biāo)準(zhǔn)類型的原型上准潭,并且規(guī)定了當(dāng)對象被轉(zhuǎn)換為原始值時應(yīng)該執(zhí)行的操作趁俊。
該方法接受一個值作為參數(shù),該值在規(guī)范中被稱為“類型提示(hint)”,分別是:number刑然、string或default寺擂,對應(yīng)的返回分別是數(shù)字、字符串活無類型偏好的值泼掠。
對于大多數(shù)標(biāo)準(zhǔn)對象怔软,數(shù)字模式有以下的特性,根據(jù)優(yōu)先級的順序排列如下:
1.調(diào)用valueOf()方法择镇,結(jié)果為原始值挡逼,則返回;
2.否則調(diào)用toString()方法腻豌,結(jié)果為原始值家坎,則返回;
3.如果再無可選值吝梅,則拋出錯誤虱疏。
對于大多數(shù)標(biāo)準(zhǔn)對象,字符串模式有以下優(yōu)先級排序:
1.調(diào)用toString()方法憔涉,結(jié)果為原始值订框,則返回;
2.否則調(diào)用valueOf()方法兜叨,結(jié)果為原始值穿扳,則返回衩侥;
3.如果再無可選值,則拋出錯誤矛物。
在大多數(shù)情況下茫死,標(biāo)準(zhǔn)對象會將默認模式按數(shù)字模式處理(除了Date對象,在這種情況下履羞,會將默認模式按字符串模式處理)
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.toStringTag:一個在調(diào)用Object.prototype.toString()方法時使用的字符串峦萎,用于創(chuàng)建對象描述。
- Symbol.unscopables:一個定義了一些不可被with語句引用的對象屬性名稱的對象集合忆首。
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