有關(guān)JavaScript的10個(gè)怪癖和秘密分享
原文作者:Andy Croxall
原文鏈接:Ten Oddities And Secrets About JavaScript
翻譯編輯:張?chǎng)涡?/A>
數(shù)據(jù)類型和定義
1. Null是個(gè)對(duì)象
JavaScript眾多類型中有個(gè)Null類型,它有個(gè)唯一的值null, 即它的字面量,定義為完全沒(méi)有任何意義的值。其表現(xiàn)得像個(gè)對(duì)象,如下檢測(cè)代碼:
alert(typeof null); //彈出 'object'
如下截圖:
盡管typeof值顯示是"object",但null并不認(rèn)為是一個(gè)對(duì)象實(shí)例。要知道,JavaScript中的值都是對(duì)象實(shí)例,每個(gè)數(shù)值都是Number對(duì)象,每個(gè)對(duì)象都是Object對(duì)象。因?yàn)閚ull是沒(méi)有值的,所以,很明顯,null不是任何東西的實(shí)例。因此,下面的值等于false。
alert(null instanceof Object); //為 false
譯者注:null還有被理解為對(duì)象占位符一說(shuō)
2. NaN是個(gè)數(shù)值
NaN本意是表示某個(gè)值不是數(shù)值,但是其本身卻又是數(shù)值,且不等于其自身,很奇怪吧,看下面的代碼:
alert(typeof NaN); //彈出 'Number'
alert(NaN === NaN); //為 false
結(jié)果如下截圖:


實(shí)際上NaN不等于任何東西。要確認(rèn)某玩意是不是NaN只能使用isNaN.
3. 無(wú)關(guān)鍵字的數(shù)組等同于false(關(guān)于Truthy和Falsy)
下面是JavaScript另一個(gè)極品怪癖:
alert(new Array() == false); //為 true
結(jié)果如下截圖:

想要知道這里發(fā)生了什么,你需要理解truthy和falsy這個(gè)概念。它們是一種true/flase字面量。在JavaScript中,所有的非Boolean型值都會(huì)內(nèi)置一個(gè)boolean標(biāo)志,當(dāng)這個(gè)值被要求有boolean行為的時(shí)候,這個(gè)內(nèi)置布爾值就會(huì)出現(xiàn),例如當(dāng)你要跟Boolean型值比對(duì)的時(shí)候。
因?yàn)樘O果不能和梨做比較,所以當(dāng)JavaScript兩個(gè)不同類型的值要求做比較的時(shí)候,它首先會(huì)將其弱化成相同的類型。false, undefined, null, 0, "", NaN都弱化成false。這種強(qiáng)制轉(zhuǎn)化并不是一直存在的,只有當(dāng)作為表達(dá)式使用的時(shí)候??聪旅孢@個(gè)簡(jiǎn)單的例子:
var someVar =0;
alert(someVar == false); //顯示 true
結(jié)果如下截圖:

上面測(cè)試中,我們?cè)噲D將數(shù)值0和boolean值false做比較,因兩者的數(shù)據(jù)類型不兼容,JavaScript自動(dòng)強(qiáng)制轉(zhuǎn)換成統(tǒng)一的等同的truthy和falsy,其中0等同于false(正如上面所提及的)。
你可能注意到了,上面一些等同false的值中并沒(méi)有空數(shù)組。只因空數(shù)組是個(gè)怪胚子:其本身實(shí)際上屬于truthy,但是當(dāng)空數(shù)組與Boolean型做比較的時(shí)候,其行為表現(xiàn)又屬于falsy。不解?這是由原因的。先舉個(gè)例子驗(yàn)證下空數(shù)組的奇怪脾氣:
var someVar = []; //空數(shù)組
alert(someVar == false); //結(jié)果 true
if (someVar) alert('hello'); //alert語(yǔ)句執(zhí)行, 所以someVar當(dāng)作true
結(jié)果如下截圖,連續(xù)彈出兩個(gè)框框:


譯者注:之所以會(huì)有這種差異,根據(jù)作者的說(shuō)法,數(shù)組內(nèi)置toString()方法,例如直接alert的時(shí)候,會(huì)以join(“,”)的形式彈出字符串,空數(shù)組自然就是空字符串,于是等同false。具體可參見(jiàn)作者另外一篇文章,《Twisted logic: understanding truthy & falsy》。不過(guò)我個(gè)人奇怪的是,像空對(duì)象,空函數(shù),弱等于true或者false的時(shí)候都顯示false,為何?真的因?yàn)閿?shù)組是個(gè)怪胎,需要特殊考慮嗎?
為避免強(qiáng)制轉(zhuǎn)換在比較方面的問(wèn)題,你可以使用強(qiáng)等于(===)代替弱等于(==)。
var someVar = 0;
alert(someVar == false); //結(jié)果 true – 0屬于falsy
alert(someVar === false); //結(jié)果 false – zero是個(gè)數(shù)值, 不是布爾值
結(jié)果如下截圖(win7 FF4):


如果你想深入探究JavaScript中類型強(qiáng)制轉(zhuǎn)換等些特有的癖好,可以參見(jiàn)官方相關(guān)的文檔規(guī)范:
section 11.9.3 of the ECMA-262
正則表達(dá)式
4. replace()可以接受回調(diào)函數(shù)
這是JavaScript最鮮為人知的秘密之一,v1.3中首次引入。大部分情況下,replace()的使用類似下面:
alert('10 13 21 48 52'.replace(/\d+/g, '*')); //用 * 替換所有的數(shù)字
這是一個(gè)簡(jiǎn)單的替換,一個(gè)字符串,一個(gè)星號(hào)。但是,如果我們希望在替換發(fā)生的時(shí)候有更多的控制,該怎么辦呢?我們只希望替換30以下的數(shù)值,該怎么辦呢?此時(shí)如果僅僅依靠正則表達(dá)式是鞭長(zhǎng)莫及的。我們需要借助回調(diào)函數(shù)的東風(fēng)對(duì)每個(gè)匹配進(jìn)行處理。
alert('10 13 21 48 52'.replace(/\d+/g, function(match) {
return parseInt(match) <30?'*' : match;
}));
當(dāng)每個(gè)匹配完成的時(shí)候,JavaScript應(yīng)用回調(diào)函數(shù),傳遞匹配內(nèi)容給match參數(shù)。然后,根據(jù)回調(diào)函數(shù)里面的過(guò)濾規(guī)則,要么返回星號(hào),要么返回匹配本身(無(wú)替換發(fā)生)。
如下截圖:

5. 正則表達(dá)式:不只是match和replace
不少javascript工程師都是只通過(guò)match和replace和正則表達(dá)式打交道。但JavaScript所定義的正則表達(dá)式相關(guān)方法遠(yuǎn)不止這兩個(gè)。
其中值得一提的是test(),其工作方式類似match(),但是返回值卻不一樣:test()返回的是布爾型,用來(lái)驗(yàn)證是否匹配,執(zhí)行速度高于match()。
alert(/\w{3,}/.test('Hello')); //彈出 'true'
上面行代碼用來(lái)驗(yàn)證字符串是否有三個(gè)以上普通字符,顯然"hello"是符合要求的,所以彈出true。
結(jié)果如下截圖:

我們還應(yīng)注意RegExp對(duì)象,你可以用此創(chuàng)建動(dòng)態(tài)正則表達(dá)式對(duì)象,例如:
function findWord(word, string) {
var instancesOfWord = string.match(new RegExp('\\b'+word+'\\b', 'ig'));
alert(instancesOfWord);
}
findWord('car', 'Carl went to buy a car but had forgotten his credit card.');
這兒,我們基于參數(shù)word動(dòng)態(tài)創(chuàng)建了匹配驗(yàn)證。這段測(cè)試代碼作用是不區(qū)分大小選的情況下選擇car這個(gè)單詞。眼睛一掃而過(guò),測(cè)試英文句子中只有一個(gè)單詞是car,因此這里的演出僅一個(gè)單詞。\b是用來(lái)表示單詞邊界的。
結(jié)果如下截圖:
函數(shù)和作用域
6. 你可以冒充作用域
作用域這玩意是用來(lái)決定什么變量是可用的,獨(dú)立的JavaScript(如JavaScript不是運(yùn)行中函數(shù)中)在window對(duì)象的全局作用域下操作,window對(duì)象在任何情況下都可以訪問(wèn)。然而函數(shù)中聲明的局部變量只能在該函數(shù)中使用。
var animal ='dog';
function getAnimal(adjective) { alert(adjective+''+this.animal); }
getAnimal('lovely'); //彈出 'lovely dog'
這兒我們的變量和函數(shù)都聲明在全局作用域中。因?yàn)閠his指向當(dāng)前作用域,在這個(gè)例子中就是window。因此,該函數(shù)尋找window.animal,也就是'dog'了。到目前為止,一切正常。然而,實(shí)際上,我們可以讓函數(shù)運(yùn)行在不同的作用域下,而忽視其本身的作用域。我們可以用一個(gè)內(nèi)置的稱為call()的方法來(lái)實(shí)現(xiàn)作用域的冒充。
var animal ='dog';
function getAnimal(adjective) { alert(adjective+''+this.animal); };
var myObj = {animal: 'camel'};
getAnimal.call(myObj, 'lovely'); //彈出 'lovely camel'
call()方法中的第一個(gè)參數(shù)可以冒充函數(shù)中的this,因此,這里的this.animal實(shí)際上就是myObj.animal,也就是'camel'了。后面的參數(shù)就作為普通參數(shù)傳給函數(shù)體。
另外一個(gè)與之相關(guān)的是apply()方法,其作用于call()一樣,不同之處在于,傳遞給函數(shù)的參數(shù)是以數(shù)組形式表示的,而不是獨(dú)立的變量們。所以,上面的測(cè)試代碼如果用apply()表示就是:
getAnimal.apply(myObj, ['lovely']); //函數(shù)參數(shù)以數(shù)組形式發(fā)送
demo頁(yè)面中,點(diǎn)擊第一個(gè)按鈕的結(jié)果如下截圖:

點(diǎn)擊第二個(gè)和第三個(gè)按鈕的結(jié)果如下:

下面這個(gè)是很OK的:
(function() { alert('hello'); })(); //彈出 'hello'
這里的解析足夠簡(jiǎn)單:聲明一個(gè)函數(shù),然后因?yàn)?)解析立即執(zhí)行它。你可能會(huì)奇怪為何要這么做(指直接屁股后面()調(diào)用),這看上去是有點(diǎn)自相矛盾的:函數(shù)包含的通常是我們想稍后執(zhí)行的代碼,而不是當(dāng)下解析即執(zhí)行的,否則,我們就沒(méi)有必要把代碼放在函數(shù)中。
另外一個(gè)執(zhí)行函數(shù)自身(self-executing functions (SEFs))的不錯(cuò)使用是為在延遲代碼中使用綁定變量值,例如事件的回調(diào)(callback),超時(shí)執(zhí)行(timeouts)和間隔執(zhí)行(intervals)。如下例子:
var someVar ='hello';
setTimeout(function() { alert(someVar); }, 1000);
var someVar ='goodbye';
Newbies在論壇里總問(wèn)這里timeout的彈出為什么是goodbye而不是hello?答案就timeout中的回調(diào)函數(shù)直到其運(yùn)行的時(shí)候才去賦值someVar變量的值。而那個(gè)時(shí)候,someVar已經(jīng)被goodbye重寫了好長(zhǎng)時(shí)間了。
SEFs提供了一個(gè)解決此問(wèn)題的方法。不是像上面一樣含蓄地指定timeout回調(diào),而是直接將someVar值以參數(shù)的形式傳進(jìn)去。效果顯著,這意味著我們傳入并孤立了someVar值,保護(hù)其無(wú)論后面是地震海嘯還是女朋友發(fā)飆咆哮都不會(huì)改變。
var someVar = 'hello';
setTimeout((function(someVar) {
returnfunction() { alert(someVar); }
})(someVar), 1000);
var someVar ='goodbye';
風(fēng)水輪流轉(zhuǎn),這次,這里的彈出就是hello了。這就是函數(shù)參數(shù)和外部變量的點(diǎn)差別了哈。
例如,最后一個(gè)按鈕點(diǎn)擊后的彈出如下:

8. FireFox以RGB格式讀與返回顏色而非Hex
直到現(xiàn)在我都沒(méi)有真正理解為何Mozilla會(huì)這樣子。為了有個(gè)清晰的認(rèn)識(shí),看下面這個(gè)例子:
<!--
#somePara { color: #f90; }
-->
<p id="somePara">Hello, world!</p>
<script>
var ie = navigator.appVersion.indexOf('MSIE') !=-1;
var p = document.getElementById('somePara');
alert(ie ? p.currentStyle.color : getComputedStyle(p, null).color);
</script>
大部分瀏覽器彈出的結(jié)果是ff9900,而FireFox的結(jié)果卻是rgb(255, 153, 0),RGB的形式。經(jīng)常,處理顏色的時(shí)候,我們需要花費(fèi)不少代碼將RGB顏色轉(zhuǎn)為Hex。
下面是上面代碼在不同瀏覽器下的結(jié)果:


9. 0.1 + 0.2 !== 0.3
這個(gè)古怪的問(wèn)題不只會(huì)出現(xiàn)在JavaScript中,這是計(jì)算機(jī)科學(xué)中一個(gè)普遍存在的問(wèn)題,影響了很多的語(yǔ)言。標(biāo)題等式輸出的結(jié)果是0.30000000000000004。
這是個(gè)被稱為機(jī)器精度的問(wèn)題。當(dāng)JavaScript嘗試執(zhí)行(0.1 + 0.2)這行代碼的時(shí)候,會(huì)把值轉(zhuǎn)換成它們喜歡的二進(jìn)制口味。這就是問(wèn)題的起源,0.1實(shí)際上并不是0.1,而是其二進(jìn)制形式。從本質(zhì)上將,當(dāng)你寫下這些值的時(shí)候,它們注定要失去精度。你可能只是希望得到個(gè)簡(jiǎn)單的兩位小數(shù),但你得到的(根據(jù)Chris Pine的注解)是二進(jìn)制浮點(diǎn)計(jì)算。好比你想把一段應(yīng)該翻譯成中文簡(jiǎn)體,結(jié)果出來(lái)的卻是繁體,其中還是有差異是不一樣的。
一般處理與此相關(guān)的問(wèn)題有兩個(gè)做法:
轉(zhuǎn)換成整數(shù)再計(jì)算,計(jì)算完畢再轉(zhuǎn)換成希望的小數(shù)內(nèi)容
調(diào)整你的邏輯,設(shè)定允許范圍為不是指定結(jié)果。
例如,我們不應(yīng)該下面這樣:
var num1=0.1, num2=0.2, shouldEqual=0.3;
alert(num1 + num2 == shouldEqual); //false
而可以試試這樣:
alert(num1 + num2 > shouldEqual - 0.001&& num1 + num2 < shouldEqual +0.001); //true
10. 未定義(undefined)可以被定義(defined)
我們以一個(gè)和風(fēng)細(xì)雨的小古怪結(jié)束。聽(tīng)起來(lái)可能有點(diǎn)奇怪,undefined并不是JavaScript中的保留字,盡管它有特殊的意義,并且是唯一的方法確定變量是否未定義。因此:
var someVar;
alert(someVar == undefined); //顯示 true
目前為止,一切看上去風(fēng)平浪靜,正常無(wú)比,但劇情總是很狗血:
undefined ="I'm not undefined!";
var someVar;
alert(someVar == undefined); //顯示 false!
這就是為什么jQuery源碼中最外部的閉包函數(shù)要有個(gè)并沒(méi)有傳入的undefined參數(shù),目的就是保護(hù)undefined不要被外部的些不良乘虛而入。
相關(guān)文章
js中將具有數(shù)字屬性名的對(duì)象轉(zhuǎn)換為數(shù)組
js中將具有數(shù)字屬性名的對(duì)象轉(zhuǎn)換為數(shù)組,雖然不太常用,但我們的確可以給對(duì)象添加以數(shù)字為屬性名的屬性2011-03-03JS判斷空對(duì)象的幾個(gè)方法大盤點(diǎn)
在做數(shù)據(jù)交互的時(shí)候,我們經(jīng)常需要判斷數(shù)據(jù)或者對(duì)象是不是為空,避免當(dāng)接口異常時(shí)候前端頁(yè)面崩潰,下面這篇文章主要給大家介紹了關(guān)于JS判斷空對(duì)象的幾個(gè)方法,需要的朋友可以參考下2022-02-02JS使用window.requestAnimationFrame()實(shí)現(xiàn)逐幀動(dòng)畫
這篇文章介紹了JS使用window.requestAnimationFrame()實(shí)現(xiàn)逐幀動(dòng)畫的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06JavaScript中的淺拷貝和深拷貝原理與實(shí)現(xiàn)淺析
這篇文章主要介紹了JavaScript中的淺拷貝和深拷貝原理與實(shí)現(xiàn),JavaScript 中的淺拷貝和深拷貝指的是在復(fù)制對(duì)象(包括對(duì)象、數(shù)組等)時(shí),是否只復(fù)制對(duì)象的引用地址或者在復(fù)制時(shí)創(chuàng)建一個(gè)新的對(duì)象2023-04-04輕松實(shí)現(xiàn)js選項(xiàng)卡切換效果
這篇文章主要幫助大家輕松實(shí)現(xiàn)js選項(xiàng)卡切換效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09淺談Javascript實(shí)現(xiàn)繼承的方法
本文給大家簡(jiǎn)單介紹了下如何在javascript中實(shí)現(xiàn)繼承的幾種方法,十分的實(shí)用,有需要的小伙伴可以參考下。2015-07-07