標籤:擷取對象 bsp console 字串類型 blog 等於 eof 排查 參數
資料類型
在介紹Symbol之前,我們簡單介紹一下JavaScript的資料類型:
JavaScript有6中資料類型,分別是:
每一種全新的事物的誕生都是為瞭解決某種問題。
設計初衷 為了探索它的設計初衷,我們聊聊一個實際的開發情境:
在一個多人合作的團隊中,程式員A寫了一個對象Object供其他人使用,有一天程式員B使用了這個對象Object,還為它添加了幾個新的屬性和方法,一切都那麼順利地完成了。
次日,測試告訴A產品有bug,A:“怎麼可能,昨天還好好的,我都沒改過任何東西啊~~~”。
無可奈何的A只要慢慢排查,最後發現,是B給對象Object添加方法的時候,其中一個方法名和A寫的一個方法名相同,被覆蓋了。
對象的屬性被覆蓋,這在日常開發中,也時常會出現,為了根本上解決命名問題,我們需要給屬性或者方法起一個獨一無二的名稱,這樣,才能從根本上防止屬性名稱衝突的問題。
這就是ES6設計出一個Symbol的初衷:解決對象的屬性名稱衝突。既然我們知道了Symbol的設計初衷,也就是知道了它的作用。接下來,我們來看看它是什麼使用的:
1 //定義一個symbol類型的變數2 let sm = Symbol();3 4 console.log(sm);5 //列印結果:Symbol()6 7 console.log(typeof sm);8 //列印結果:symbol
從上面代碼案例看到,我們用一個Symbol( )函數來建立一個symbol類型的變數,我們列印了一下變數sm,得到的結果是控制台輸出:Symbol( ),它代表著一個獨一無二的值,雖然我們看不到它長什麼樣子,但基本上,它有點類似字串。
接著,我們用typeof來檢測一下變數sm的類型,得到的結果是:symbol。
怎樣判斷是它是獨一無二的值呢?我們來看看:
1 let sm1 = Symbol();2 let sm2 = Symbol();3 4 sm1 === sm2 //結果:false5 6 console.log(sm1);//結果:Symbol()7 console.log(sm2);//結果:Symbol()
我們定義兩個symbol類型的變數sm1,sm2,然後用全等符號===進行比較,得到的是false。也就是他們都是獨一無二的值,並不相等。
接著,我們分別列印兩個變數,控制台輸出的都是Symbol( ),看起來長得一模一樣,實際是不相等的。
兩個不一樣的值,控制台輸出的一樣,這樣無疑給我們開發調試帶來一定的不便,有沒有辦法讓他們看起來不一樣呢?
有的,Symbo( )函數接受參數,用於對執行個體值的描述。我們試試看:
1 let sm1 = Symbol(‘sm1‘);2 let sm2 = Symbol(‘sm2‘);3 4 console.log(sm1);5 //結果:Symbol(sm1)6 7 console.log(sm2);8 //結果:Symbol(sm2)
用字串sm1和sm2作為參數,結果列印出來的變數sm1和sm2就是Symbol(sm1)和Symbol(sm2),等於加上了描述,很容易區分出來。
需要注意的是,即使參數一樣,描述一樣,得到的兩個值也是不相等的,不信我們來看看:
1 let sm1 = Symbol(‘sm‘);2 let sm2 = Symbol(‘sm‘);3 4 sm1 === sm2 //結果:false
即使兩個變數的描述都是“sm”,但是終究對應的值還是不一樣的,symbol永遠都是獨一無二的值,謹記。
瞭解了這幾個symbol類型值的特點後,前面說到,Symbol是為瞭解決對象屬性名稱衝突的問題,那麼我們就結合對象,來學習:
1 let name = Symbol(); 2 let person = { 3 [name]:"張三" 4 }; 5 6 7 console.log(person[name]); 8 //結果:張三 9 10 console.log(person.name);11 //結果:undefined
看代碼,從上往下擼,首先,我們定義一個symbol類型的變數name,它作為一個對象person的屬性,對應的值是“張三”;
接著,我們用兩種方式擷取name的值,第一種用中括弧的形式[ name ]能正確擷取到,第二種用點運算子的形式,擷取失敗。原因是:當symbol值作為對象的屬性名稱的時候,不能用點運算子擷取對應的值。
此外還有一點要注意,把一個symbol類型的值作為對象的屬性名稱的時候,一定要用中括弧[ ],不能用點運算子,因為用點運算子的話,會導致javascript把後面的屬性名稱為理解為一個字串類型,而不是symbol類型。具體看代碼:
1 let name = Symbol();2 let person = {};3 4 person.name = "張三";5 6 person[name]; //結果:undefined7 person[‘name‘]; //結果:張三8 person.name; //結果:張三
其中變數name是symbol,但是給person對象設定屬性的時候,用的是點運算子person.name,而不是中括弧person[ name ],這會有什麼後果呢?這就會導致person對象中的屬性name實際上是字串類型的,這也就解釋了最後三行代碼的列印結果了。
person[ name ]這句代碼相當於要求javascript去person對象內找一個symbol類型的屬性name,不好意思,沒有,找不到。person對象只有一個字串類型的屬性name;所以,如果用person[‘name’]或者peroson.name擷取的話,就能找到對應的屬性name了。
原來用symbol類型的值作為對象的屬性也有這麼多講究,好吧,我認了!誰叫你是ECMAScript呢,你說了算!
用symbol類型的屬性除了能保證是獨一無二的值,還有什麼其他的特點嗎?
屬性名稱的遍曆
當symbol類型的值作為屬性名稱的時候,該屬性是不會出現在for...in和for...of中的,也不會被Object.keys( )擷取到。我們來看案例:
1 //定義一個symbol類型的變數name 2 let name = Symbol(); 3 4 //定義一個含有兩種類型屬性的對象 5 let person = { 6 [name]:"張三", //symbol類型 7 "age":12 //string類型 8 }; 9 10 Object.keys(person);//結果:["age"]11 12 for(let key in person){13 console.log(key);14 }15 //列印結果:age
person對象有兩個屬性,屬性名稱有兩種類型:symbol類型和string字串類型,我們通過keys( )函數擷取到的屬性,只有屬性age,我們通過for...in迴圈列印出來,也只列印出了屬性age。(for...of也屬於ES6的新增知識,後面會專門有一節介紹),以上幾種方法都無法擷取到symbol類型的屬性。
getOwnPropertySymbols( )函數
如果我們硬是想要擷取symbol類型的屬性怎麼辦?我們可以用一個方法:Object.getOwnPropertySymbols( ),它會找到symbol類型的屬性並且返回一個數組,數組的成員就是symbol類型的屬性值,看代碼:
1 //定義兩個symbol類型的變數name,age 2 let name = Symbol("name"); 3 let age = Symbol("age"); 4 5 6 let person = { 7 [name]:"張三", //symbol類型 8 [age]:12 //symbol類型 9 };10 11 Object.getOwnPropertySymbols(person);12 //結果:[Symbol(name), Symbol(age)]
person對象的兩個屬性都是symbol類型的,我們也知道用Object.keys( )和for...in都無法擷取到它,我們就用getOwnPropertySymbols( )方法來,結果成功了,得到一個數組,數組的內容就是兩個symbol類型變數對應的值Symbol(name)和 Symbol(age)。
Reflect.ownKeys( )函數
這樣的話,擷取字串類型的屬性和擷取symbol類型的屬性要分開兩種不同的方式來擷取,難免有有時候會很不方便,有木有什麼辦法讓我們一次性擷取所有類型的屬性,不管它是字串類型還是symbol類型呢?
有的,我們可以用Reflect.ownKeys( )方法實現:
1 //定義一個對象,含有兩種類型的屬性2 let person = {3 [Symbol(‘name‘)]:"張三",4 "age": 215 };6 7 Reflect.ownKeys(person);8 //結果:["age",Symbol(name)]
上面的代碼中,我們先定義一個對象person,它含有兩個屬性,一個是symbol類型的,一個是字串類型的。
接著,我們將對象person傳入Reflect.ownKeys( )函數中,函數就會給我們返回一個數組,數組的內容便是對象的屬性,包括symbol類型和字串類型。
此外,Symbol還提供了兩個很實用的函數,我們來學習一下。
Symbol.for( )函數
函數作用:根據參數名,去全域環境中搜尋是否有以該參數為名的symbol值,有就返回它,沒有就以該參數名來建立一個新的symbol值。
文字描述總是那麼乏力,所以要加上案例:
1 let n1 = Symbol.for(‘name‘);2 let n2 = Symbol.for(‘name‘);3 console.log(n1 === n2);4 //結果:true
上面最後一句代碼,我們用全相等來對兩個變數進行對比,得到:true;說明n2就是n1,兩者相等。
但是細心地同學會注意到,上面的代碼中,定義兩個symbol值得時候用的都是Symbol.for( ),而不是用Symbol( )。
兩者在建立symbol值的時候有什麼不同嗎?它們的區別是:Symbol.for( )建立的symbol值會被登記在全域環境中,供以後用Symbol.for( )來搜尋,而Symbol( )建立的變數就沒有這樣的效果了。
也就是說,用Symbol( )建立的symbol值,以後用Symbol.for( )去搜尋,是找不到的。不信,我們來示範一下,還是用上面的代碼,我們稍微改一下第一行:
1 let n1 = Symbol(‘name‘);2 let n2 = Symbol.for(‘name‘);3 console.log(n1 === n2);4 //結果:false
第一行我們用Symbol( )來建立的一個symbol值,按照上述的所說的,它不會被登記在全域環境中;所以,第二行我們用Symbol.for( )去找的時候,是找不到的,找不到怎麼辦?此時Symbol.for( )會自動建立一個新的symbol值,也就是說n1,n2是不同的兩個symbol值了,所以進行全相等比較的時候,會返回:false。
Symbol.keyFor( )函數
函數作用:返回一個以被登記在全域環境中的symbol值的key,沒有就返回undefined。注意這句話的一個關鍵詞:“被登記在全域環境中”,也就是說這個symbol值是被Symbol.for( )建立的,不是被Symbol( )建立的。
1 let n1 = Symbol.for(‘name‘);2 Symbol.KeyFor(n1);3 //結果:name
上面的變數n1是被Symbol.for( )建立,不是被Symbol( )建立的,所以用Symbol.keyFor( )去找,是能找到的,會返回這個symbol值的key,也就是它的描述:name。
我們再對上面的案例稍做修改:
1 2 let n1 = Symbol(‘name‘);3 Symbol.KeyFor(n1);4 //結果:undefined
這段代碼的變數n1是用Symbol( )建立的,最後的結果是undefined;這就證明了兩個知識點:1、Symbol( )建立symbol值不會被登記在全域環境中供Symbol.for( )和Symbol.keyFor( )搜尋;2、Symbol.keyFor( )函數在全域環境中找不到對應的symbol,就回返回undefined。
以上就是ES6給我們帶來的第七種資料類型:Symbol;Symbol還有更多的小知識,初學者或者新手只要掌握理解上面的幾點就足夠日常使用了,只要進了門,往後的深入都是自然而然的,任何知識的學習都一樣。
本節小結
總結:JavaScript有了第七種資料類型:Symbol,建立一個獨一無二的值;它用於對象的屬性,設計初衷是為了避免對象屬性衝突的問題。要擷取對象symbol類型的屬性,要用Object.getOwnPropertySymbols( );還提供了Symbol.for( )和Symbol.keyFor( )方法用於搜尋對應的symbol值。
JavaScript有了一種全新的資料類型:Symbol