你真的掌握變量和類型了嗎(二)類型轉(zhuǎn)換

六、還有哪些引用類型

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)用方法告组,就會自動進行裝箱和拆箱操作煤伟,相同的,我們使用NumberBoolean類型時木缝,也會發(fā)生這個過程便锨。

從引用類型到基本類型的轉(zhuǎn)換,也就是拆箱的過程中我碟,會遵循ECMAScript規(guī)范規(guī)定的toPrimitive原則放案,一般會調(diào)用引用類型的valueOftoString方法,你也可以直接重寫toPeimitive方法矫俺。一般轉(zhuǎn)換成不同類型的值遵循的原則不同吱殉,例如:

  • 引用類型轉(zhuǎn)換為Number類型,先調(diào)用valueOf厘托,再調(diào)用toString
  • 引用類型轉(zhuǎn)換為String類型友雳,先調(diào)用toString,再調(diào)用valueOf

valueOftoString都不存在铅匹,或者沒有返回基本類型押赊,則拋出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)用包裝類型的valueOftoString,實現(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展箱、nullBoolean比較,雖然undefined蹬昌、nullfalse都很容易被想象成假值,但是他們比較結(jié)果是false攀隔,原因是false首先被轉(zhuǎn)換成0

undefined == false; // false
null == false; // false
  • 3.String 和 Number

StringNumber比較皂贩,先將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ù)組元素為nullundefined時步淹,該元素被當(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"這樣的判斷箕戳。

參考

小結(jié)

希望你閱讀本篇文章后可以達到以下幾點:

  • 了解JavaScript中的變量在內(nèi)存中的具體存儲形式,可對應(yīng)實際場景
  • 搞懂小數(shù)計算不精確的底層原因
  • 了解可能發(fā)生隱式類型轉(zhuǎn)換的場景以及轉(zhuǎn)換原則
  • 掌握判斷JavaScript數(shù)據(jù)類型的方式和底層原理

文中如有錯誤国撵,歡迎在評論區(qū)指正陵吸,如果這篇文章幫助到了你,歡迎點贊和關(guān)注介牙。


歡迎大家到公眾號: you的日常 閱讀壮虫,體驗更好哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末环础,一起剝皮案震驚了整個濱河市囚似,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌线得,老刑警劉巖饶唤,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贯钩,居然都是意外死亡募狂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門魏保,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熬尺,“玉大人,你說我怎么就攤上這事谓罗。” “怎么了季二?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵檩咱,是天一觀的道長。 經(jīng)常有香客問我胯舷,道長刻蚯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任桑嘶,我火速辦了婚禮炊汹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘逃顶。我一直安慰自己讨便,他們只是感情好充甚,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著霸褒,像睡著了一般伴找。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上废菱,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天技矮,我揣著相機與錄音,去河邊找鬼殊轴。 笑死衰倦,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旁理。 我是一名探鬼主播樊零,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼韧拒!你這毒婦竟也來了淹接?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤叛溢,失蹤者是張志新(化名)和其女友劉穎塑悼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體楷掉,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡厢蒜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了烹植。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斑鸦。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖草雕,靈堂內(nèi)的尸體忽然破棺而出巷屿,到底是詐尸還是另有隱情,我是刑警寧澤墩虹,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布嘱巾,位于F島的核電站,受9級特大地震影響诫钓,放射性物質(zhì)發(fā)生泄漏旬昭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一菌湃、第九天 我趴在偏房一處隱蔽的房頂上張望问拘。 院中可真熱鬧,春花似錦、人聲如沸骤坐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽或油。三九已至寞忿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顶岸,已是汗流浹背腔彰。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留辖佣,地道東北人霹抛。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像卷谈,于是被迫代替她去往敵國和親杯拐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容