【ES6 筆記】Symbol和Symbol屬性

回憶一下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] : '歐陽不乖'
}
//將屬性設置為只讀
Object.defineProperty( person, firstName, { writable : false});
console.log(person[firstName]); //'歐陽不乖'

Symbol共享體系

如果想創(chuàng)建一個可共享的Symbol,要使用Symbol.for()方法。它只接受一個參數(shù),也就是即將創(chuàng)建的Symbol的字符串標識符,這個參數(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共享有關的特性:可以使用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全局注冊表是一個類似全局作用域的共享環(huán)境,也就是說你不能假設目前環(huán)境中存在哪些鍵

Symbol與類型強制轉換

由于其他類型沒有與Symbol邏輯等價的值,所以不能將Symbol強制轉換為字符串或是數(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與一個字符串拼接會導致程序拋出錯誤:

let uidDesc = Symbol.for('uid') + ''; //報錯

Symbol不可以被轉為字符串,同樣也不能轉為數(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
*/

我們可以按照自己的喜好任意重構Symbol.hasInstance,但是改寫源碼會造成不可預期的后果,所以請在必要的情況下只改寫自己聲明的函數(shù)Symbol.hasInstance屬性

  • Symbol.isConcatSpreadable:一個布爾值,用于表示當傳遞一個集合作為Array.prototype.concat()方法的參數(shù)時,是否應該將合集內(nèi)的元素規(guī)整到同一層級。
    JS數(shù)組的concat方法被設計用于拼接兩個數(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ù)值型屬性值應該被獨立添加到concat調(diào)用的結果中。這個屬性默認情況下不會出現(xià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"]

// 假設將 [Symbol.isConcatSpreadable]:false改為這樣,那么運行結果就變?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的元素將字符串分切,并將結果存入數(shù)組
    在ES6之前,以上4個方法無法使用開發(fā)者自定義的對象來替代正則表達式進行字符串匹配。而在ES6中定義了與上邊4個方法相對應的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:該方法被定義在每一個標準類型的原型上,并且規(guī)定了當對象被轉換為原始值時應該執(zhí)行的操作。
    該方法接受一個值作為參數(shù),該值在規(guī)范中被稱為“類型提示(hint)”,分別是:number、string或default,對應的返回分別是數(shù)字、字符串活無類型偏好的值。
    對于大多數(shù)標準對象,數(shù)字模式有以下的特性,根據(jù)優(yōu)先級的順序排列如下:
    1.調(diào)用valueOf()方法,結果為原始值,則返回;
    2.否則調(diào)用toString()方法,結果為原始值,則返回;
    3.如果再無可選值,則拋出錯誤。
    對于大多數(shù)標準對象,字符串模式有以下優(yōu)先級排序:
    1.調(diào)用toString()方法,結果為原始值,則返回;
    2.否則調(diào)用valueOf()方法,結果為原始值,則返回;
    3.如果再無可選值,則拋出錯誤。
    在大多數(shù)情況下,標準對象會將默認模式按數(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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,535評論 0 13
  • 概述 ES5的對象屬性名都是字符串,這容易造成屬性名的沖突。比如,你使用了一個他人提供的對象,但又想為這個對象添加...
    oWSQo閱讀 590評論 1 3
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,674評論 0 4
  • 一、高手的暗箱:利用規(guī)律,放大努力 沒有一個人是僅憑借努力、天賦、機遇而獲得巨大 成功的,躍遷式的成功都是利用了更...
    造塔人閱讀 432評論 0 0
  • 我為什么而活 為了山澗潺潺流動的清泉 為了漫山姹紫嫣紅的花朵 為了林間婉轉悅耳的天籟 我為什么而活 為了體驗和感知...
    清羽33閱讀 412評論 6 9

友情鏈接更多精彩內(nèi)容