六、還有哪些引用類型
在
ECMAScript
中辣恋,引用類型是一種數(shù)據(jù)結(jié)構(gòu)惫撰,用于將數(shù)據(jù)和功能組織在一起羔沙。
我們通常所說的對象,就是某個特定引用類型的實例厨钻。
在ECMAScript
關(guān)于類型的定義中扼雏,只給出了Object
類型,實際上夯膀,我們平時使用的很多引用類型的變量诗充,并不是由Object
構(gòu)造的,但是它們原型鏈的終點都是Object
诱建,這些類型都屬于引用類型蝴蜓。
-
Array
數(shù)組 -
Date
日期 -
RegExp
正則 -
Function
函數(shù)
6.1 包裝類型
為了便于操作基本類型值,ECMAScript
還提供了幾個特殊的引用類型俺猿,他們是基本類型的包裝類型:
Boolean
Number
String
注意包裝類型和原始類型的區(qū)別:
true === new Boolean(true); // false
123 === new Number(123); // false
"ConardLi" === new String("ConardLi"); // false
console.log(typeof new String("ConardLi")); // object
console.log(typeof "ConardLi"); // string
引用類型和包裝類型的主要區(qū)別就是對象的生存期茎匠,使用 new 操作符創(chuàng)建的引用類型的實例,在執(zhí)行流離開當(dāng)前作用域之前都一直保存在內(nèi)存中押袍,而自基本類型則只存在于一行代碼的執(zhí)行瞬間诵冒,然后立即被銷毀,這意味著我們不能在運行時為基本類型添加屬性和方法谊惭。
var name = "ConardLi";
name.color = "red";
console.log(name.color); // undefined
6.2 裝箱和拆箱
裝箱轉(zhuǎn)換:把基本類型轉(zhuǎn)換為對應(yīng)的包裝類型
拆箱操作:把引用類型轉(zhuǎn)換為基本類型
既然原始類型不能擴展屬性和方法汽馋,那么我們是如何使用原始類型調(diào)用方法的呢否过?
每當(dāng)我們操作一個基礎(chǔ)類型時,后臺就會自動創(chuàng)建一個包裝類型的對象惭蟋,從而讓我們能夠調(diào)用一些方法和屬性苗桂,例如下面的代碼:
var name = "ConardLi";
var name2 = name.substring(2);
實際上發(fā)生了以下幾個過程:
- 創(chuàng)建一個
String
的包裝類型實例 - 在實例上調(diào)用
substring
方法 - 銷毀實例
也就是說,我們使用基本類型調(diào)用方法告组,就會自動進行裝箱和拆箱操作煤伟,相同的,我們使用Number
和Boolean
類型時木缝,也會發(fā)生這個過程便锨。
從引用類型到基本類型的轉(zhuǎn)換,也就是拆箱的過程中我碟,會遵循ECMAScript規(guī)范
規(guī)定的toPrimitive
原則放案,一般會調(diào)用引用類型的valueOf
和toString
方法,你也可以直接重寫toPeimitive
方法矫俺。一般轉(zhuǎn)換成不同類型的值遵循的原則不同吱殉,例如:
- 引用類型轉(zhuǎn)換為
Number
類型,先調(diào)用valueOf
厘托,再調(diào)用toString
- 引用類型轉(zhuǎn)換為
String
類型友雳,先調(diào)用toString
,再調(diào)用valueOf
若valueOf
和toString
都不存在铅匹,或者沒有返回基本類型押赊,則拋出TypeError
異常。
const obj = {
valueOf: () => {
console.log("valueOf");
return 123;
},
toString: () => {
console.log("toString");
return "ConardLi";
}
};
console.log(obj - 1); // valueOf 122
console.log(`${obj}ConardLi`); // toString ConardLiConardLi
const obj2 = {
[Symbol.toPrimitive]: () => {
console.log("toPrimitive");
return 123;
}
};
console.log(obj2 - 1); // valueOf 122
const obj3 = {
valueOf: () => {
console.log("valueOf");
return {};
},
toString: () => {
console.log("toString");
return {};
}
};
console.log(obj3 - 1);
// valueOf
// toString
// TypeError
除了程序中的自動拆箱和自動裝箱包斑,我們還可以手動進行拆箱和裝箱操作流礁。我們可以直接調(diào)用包裝類型的valueOf
或toString
,實現(xiàn)拆箱操作:
var num = new Number("123");
console.log(typeof num.valueOf()); //number
console.log(typeof num.toString()); //string
七罗丰、類型轉(zhuǎn)換
因為JavaScript
是弱類型的語言神帅,所以類型轉(zhuǎn)換發(fā)生非常頻繁,上面我們說的裝箱和拆箱其實就是一種類型轉(zhuǎn)換丸卷。
類型轉(zhuǎn)換分為兩種枕稀,隱式轉(zhuǎn)換即程序自動進行的類型轉(zhuǎn)換,強制轉(zhuǎn)換即我們手動進行的類型轉(zhuǎn)換谜嫉。
強制轉(zhuǎn)換這里就不再多提及了萎坷,下面我們來看看讓人頭疼的可能發(fā)生隱式類型轉(zhuǎn)換的幾個場景,以及如何轉(zhuǎn)換:
7.1 類型轉(zhuǎn)換規(guī)則
如果發(fā)生了隱式轉(zhuǎn)換沐兰,那么各種類型互轉(zhuǎn)符合下面的規(guī)則:
7.2 if 語句和邏輯語句
在if
語句和邏輯語句中哆档,如果只有單個變量,會先將變量轉(zhuǎn)換為Boolean
值住闯,只有下面幾種情況會轉(zhuǎn)換成false
瓜浸,其余被轉(zhuǎn)換成true
:
null;
undefined;
("");
NaN;
0;
false;
7.3 各種運數(shù)學(xué)算符
我們在對各種非Number
類型運用數(shù)學(xué)運算符(- * /
)時澳淑,會先將非Number
類型轉(zhuǎn)換為Number
類型;
1 - true; // 0
1 - null; // 1
1 * undefined; // NaN
2 * ["5"]; // 10
注意+
是個例外,執(zhí)行+
操作符時:
- 1.當(dāng)一側(cè)為
String
類型插佛,被識別為字符串拼接杠巡,并會優(yōu)先將另一側(cè)轉(zhuǎn)換為字符串類型。 - 2.當(dāng)一側(cè)為
Number
類型雇寇,另一側(cè)為原始類型氢拥,則將原始類型轉(zhuǎn)換為Number
類型。 - 3.當(dāng)一側(cè)為
Number
類型锨侯,另一側(cè)為引用類型嫩海,將引用類型和Number
類型轉(zhuǎn)換成字符串后拼接。
123 + "123"; // 123123 (規(guī)則1)
123 + null; // 123 (規(guī)則2)
123 + true; // 124 (規(guī)則2)
123 + {}; // 123[object Object] (規(guī)則3)
7.4 ==
使用==
時囚痴,若兩側(cè)類型相同叁怪,則比較結(jié)果和===
相同,否則會發(fā)生隱式轉(zhuǎn)換深滚,使用==
時發(fā)生的轉(zhuǎn)換可以分為幾種不同的情況(只考慮兩側(cè)類型不同):
- 1.NaN
NaN
和其他任何類型比較永遠返回false
(包括和他自己)奕谭。
NaN == NaN; // false
- 2.Boolean
Boolean
和其他任何類型比較,Boolean
首先被轉(zhuǎn)換為Number
類型成箫。
true == 1; // true
true == "2"; // false
true == ["1"]; // true
true == ["2"]; // false
這里注意一個可能會弄混的點:
undefined展箱、null
和Boolean
比較,雖然undefined蹬昌、null
和false
都很容易被想象成假值,但是他們比較結(jié)果是false
攀隔,原因是false
首先被轉(zhuǎn)換成0
:
undefined == false; // false
null == false; // false
- 3.String 和 Number
String
和Number
比較皂贩,先將String
轉(zhuǎn)換為Number
類型。
123 == "123"; // true
"" == 0; // true
- 4.null 和 undefined
null == undefined
比較結(jié)果是true
昆汹,除此之外明刷,null、undefined
和其他任何結(jié)果的比較值都為false
满粗。
null == undefined; // true
null == ""; // false
null == 0; // false
null == false; // false
undefined == ""; // false
undefined == 0; // false
undefined == false; // false
- 5.原始類型和引用類型
當(dāng)原始類型和引用類型做比較時辈末,對象類型會依照ToPrimitive
規(guī)則轉(zhuǎn)換為原始類型:
"[object Object]" == {}; // true
"1,2,3" == [1, 2, 3]; // true
來看看下面這個比較:
[] == ![]; // true
!
的優(yōu)先級高于==
,![]
首先會被轉(zhuǎn)換為false
映皆,然后根據(jù)上面第三點挤聘,false
轉(zhuǎn)換成Number
類型0
,左側(cè)[]
轉(zhuǎn)換為0
捅彻,兩側(cè)比較相等组去。
([null] ==
false[undefined]) == // true
false; // true
根據(jù)數(shù)組的ToPrimitive
規(guī)則,數(shù)組元素為null
或undefined
時步淹,該元素被當(dāng)做空字符串處理从隆,所以[null]诚撵、[undefined]
都會被轉(zhuǎn)換為0
。
所以键闺,說了這么多寿烟,推薦使用===
來判斷兩個值是否相等...
7.5 一道有意思的面試題
一道經(jīng)典的面試題,如何讓:a == 1 && a == 2 && a == 3
辛燥。
根據(jù)上面的拆箱轉(zhuǎn)換韧衣,以及==
的隱式轉(zhuǎn)換,我們可以輕松寫出答案:
const a = {
value: [3, 2, 1],
valueOf: function() {
return this.value.pop();
}
};
八购桑、判斷 JavaScript 數(shù)據(jù)類型的方式
8.1 typeof
適用場景
typeof
操作符可以準(zhǔn)確判斷一個變量是否為下面幾個原始類型:
typeof "ConardLi"; // string
typeof 123; // number
typeof true; // boolean
typeof Symbol(); // symbol
typeof undefined; // undefined
你還可以用它來判斷函數(shù)類型:
typeof function() {}; // function
不適用場景
當(dāng)你用typeof
來判斷引用類型時似乎顯得有些乏力了:
typeof []; // object
typeof {}; // object
typeof new Date(); // object
typeof /^\d*$/; // object
除函數(shù)外所有的引用類型都會被判定為object
畅铭。
另外typeof null === 'object'
也會讓人感到頭痛,這是在JavaScript
初版就流傳下來的bug
勃蜘,后面由于修改會造成大量的兼容問題就一直沒有被修復(fù)...
8.2 instanceof
instanceof
操作符可以幫助我們判斷引用類型具體是什么類型的對象:
[] instanceof Array; // true
new Date() instanceof Date; // true
new RegExp() instanceof RegExp; // true
我們先來回顧下原型鏈的幾條規(guī)則:
- 1.所有引用類型都具有對象特性硕噩,即可以自由擴展屬性
- 2.所有引用類型都具有一個
__proto__
(隱式原型)屬性,是一個普通對象 - 3.所有的函數(shù)都具有
prototype
(顯式原型)屬性缭贡,也是一個普通對象 - 4.所有引用類型
__proto__
值指向它構(gòu)造函數(shù)的prototype
- 5.當(dāng)試圖得到一個對象的屬性時炉擅,如果變量本身沒有這個屬性,則會去他的
__proto__
中去找
[] instanceof Array
實際上是判斷Array.prototype
是否在[]
的原型鏈上阳惹。
所以谍失,使用instanceof
來檢測數(shù)據(jù)類型,不會很準(zhǔn)確莹汤,這不是它設(shè)計的初衷:
[] instanceof Object // true
function(){} instanceof Object // true
另外快鱼,使用instanceof
也不能檢測基本數(shù)據(jù)類型,所以instanceof
并不是一個很好的選擇纲岭。
8.3 toString
上面我們在拆箱操作中提到了toString
函數(shù)抹竹,我們可以調(diào)用它實現(xiàn)從引用類型的轉(zhuǎn)換。
每一個引用類型都有
toString
方法止潮,默認情況下窃判,toString()
方法被每個Object
對象繼承。如果此方法在自定義對象中未被覆蓋喇闸,toString()
返回"[object type]"
袄琳,其中type
是對象的類型。
const obj = {};
obj.toString(); // [object Object]
注意燃乍,上面提到了如果此方法在自定義對象中未被覆蓋
唆樊,toString
才會達到預(yù)想的效果,事實上橘沥,大部分引用類型比如Array窗轩、Date、RegExp
等都重寫了toString
方法座咆。
我們可以直接調(diào)用Object
原型上未被覆蓋的toString()
方法痢艺,使用call
來改變this
指向來達到我們想要的效果仓洼。
8.4 jquery
我們來看看jquery
源碼中如何進行類型判斷:
var class2type = {};
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );
type: function( obj ) {
if ( obj == null ) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj) ] || "object" :
typeof obj;
}
isFunction: function( obj ) {
return jQuery.type(obj) === "function";
}
原始類型直接使用typeof
,引用類型使用Object.prototype.toString.call
取得類型堤舒,借助一個class2type
對象將字符串多余的代碼過濾掉色建,例如[object function]
將得到array
,然后在后面的類型判斷舌缤,如isFunction
直接可以使用jQuery.type(obj) === "function"
這樣的判斷箕戳。
參考
- http://www.ecma-international.org/ecma-262/9.0/index.html
- https://while.dev/articles/explaining-truthy-falsy-null-0-and-undefined-in-typescript/
- https://github.com/mqyqingfeng/Blog/issues/28
- https://juejin.im/post/5bc5c752f265da0a9a399a62
- https://juejin.im/post/5bbda2b36fb9a05cfd27f55e
- 《JS 高級程序設(shè)計》
小結(jié)
希望你閱讀本篇文章后可以達到以下幾點:
- 了解
JavaScript
中的變量在內(nèi)存中的具體存儲形式,可對應(yīng)實際場景 - 搞懂小數(shù)計算不精確的底層原因
- 了解可能發(fā)生隱式類型轉(zhuǎn)換的場景以及轉(zhuǎn)換原則
- 掌握判斷
JavaScript
數(shù)據(jù)類型的方式和底層原理
文中如有錯誤国撵,歡迎在評論區(qū)指正陵吸,如果這篇文章幫助到了你,歡迎點贊和關(guān)注介牙。
歡迎大家到公眾號: you的日常
閱讀壮虫,體驗更好哦。