原文作者:Andy Croxall
原文連結:Ten Oddities And Secrets About JavaScript
翻譯編輯:張鑫旭
資料類型和定義
1. Null是個對象
JavaScript眾多類型中有個Null類型,它有個唯一的值null, 即它的字面量,定義為完全沒有任何意義的值。其表現得像個對象,如下檢測代碼:
複製代碼 代碼如下:alert(typeof null); //彈出 'object'
如下:
儘管typeof值顯示是"object",但null並不認為是一個對象執行個體。要知道,JavaScript中的值都是對象執行個體,每個數值都是Number對象,每個對象都是Object對象。因為null是沒有值的,所以,很明顯,null不是任何東西的執行個體。因此,下面的值等於false。
複製代碼 代碼如下:alert(null instanceof Object); //為 false
譯者註:null還有被理解為對象預留位置一說
2. NaN是個數值
NaN本意是表示某個值不是數值,但是其本身卻又是數值,且不等於其自身,很奇怪吧,看下面的代碼: 複製代碼 代碼如下:alert(typeof NaN); //彈出 'Number'
alert(NaN === NaN); //為 false
結果如下:
實際上NaN不等於任何東西。要確認某玩意是不是NaN只能使用isNaN.
3. 無關鍵字的數組等同於false(關於Truthy和Falsy)
下面是JavaScript另一個極品怪癖:
複製代碼 代碼如下:alert(new Array() == false); //為 true
結果如下:
想要知道這裡發生了什麼,你需要理解truthy和falsy這個概念。它們是一種true/flase字面量。在JavaScript中,所有的非Boolean型值都會內建一個boolean標誌,當這個值被要求有boolean行為的時候,這個內建布爾值就會出現,例如當你要跟Boolean型值比對的時候。
因為蘋果不能和梨做比較,所以當JavaScript兩個不同類型的值要求做比較的時候,它首先會將其弱化成相同的類型。false, undefined, null, 0, "", NaN都弱化成false。這種強制轉化並不是一直存在的,只有當作為運算式使用的時候。看下面這個簡單的例子:
複製代碼 代碼如下:var someVar =0;
alert(someVar == false); //顯示 true
結果如下:
上面測試中,我們試圖將數值0和boolean值false做比較,因兩者的資料類型不相容,JavaScript自動強制轉換成統一的等同的truthy和falsy,其中0等同於false(正如上面所提及的)。
你可能注意到了,上面一些等同false的值中並沒有空數組。只因空數組是個怪胚子:其本身實際上屬於truthy,但是當空數組與Boolean型做比較的時候,其行為表現又屬於falsy。不解?這是由原因的。先舉個例子驗證下空數組的奇怪脾氣:
複製代碼 代碼如下:var someVar = []; //空數組
alert(someVar == false); //結果 true
if (someVar) alert('hello'); //alert語句執行, 所以someVar當作true
結果如下,連續彈出兩個框框:
譯者註:之所以會有這種差異,根據作者的說法,數組內建toString()方法,例如直接alert的時候,會以join(“,”)的形式彈出字串,空數組自然就是Null 字元串,於是等同false。具體可參見作者另外一篇文章,《Twisted logic: understanding truthy & falsy》。不過我個人奇怪的是,像Null 物件,空函數,弱等於true或者false的時候都顯示false,為何?真的因為數組是個怪胎,需要特殊考慮嗎?
為避免強制轉換在比較方面的問題,你可以使用強等於(===)代替弱等於(==)。
複製代碼 代碼如下:var someVar = 0;
alert(someVar == false); //結果 true – 0屬於falsy
alert(someVar === false); //結果 false – zero是個數值, 不是布爾值
結果如下(win7 FF4):
如果你想深入探究JavaScript中類型強制轉換等些特有的癖好,可以參見官方相關的文檔規範:
section 11.9.3 of the ECMA-262
Regex
4. replace()可以接受回呼函數
這是JavaScript最鮮為人知的秘密之一,v1.3中首次引入。大部分情況下,replace()的使用類似下面:
複製代碼 代碼如下:alert('10 13 21 48 52'.replace(/\d+/g, '*')); //用 * 替換所有的數字
這是一個簡單的替換,一個字串,一個星號。但是,如果我們希望在替換髮生的時候有更多的控制,該怎麼辦呢?我們只希望替換30以下的數值,該怎麼辦呢?此時如果僅僅依靠Regex是鞭長莫及的。我們需要藉助回呼函數的東風對每個匹配進行處理。 複製代碼 代碼如下:alert('10 13 21 48 52'.replace(/\d+/g, function(match) {
return parseInt(match) <30?'*' : match;
}));
當每個匹配完成的時候,JavaScript應用回呼函數,傳遞匹配內容給match參數。然後,根據回呼函數裡面的過濾規則,要麼返回星號,要麼返回匹配本身(無替換髮生)。
如下:
5. Regex:不只是match和replace
不少javascript工程師都是只通過match和replace和Regex打交道。但JavaScript所定義的Regex相關方法遠不止這兩個。
其中值得一提的是test(),其工作方式類似match(),但是傳回值卻不一樣:test()返回的是布爾型,用來驗證是否匹配,執行速度高於match()。 複製代碼 代碼如下:alert(/\w{3,}/.test('Hello')); //彈出 'true'
上面行代碼用來驗證字串是否有三個以上一般字元,顯然"hello"是符合要求的,所以彈出true。
結果如下:
我們還應注意RegExp對象,你可以用此建立動態Regex對象,例如:
複製代碼 代碼如下: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.');
這兒,我們基於參數word動態建立了匹配驗證。這段測試代碼作用是不區分大小選的情況下選擇car這個單詞。眼睛一掃而過,測試英文句子中只有一個單詞是car,因此這裡的演出僅一個單詞。\b是用來表示單詞邊界的。
結果如下:
函數和範圍
6. 你可以冒充範圍
範圍這玩意是用來決定什麼變數是可用的,獨立的JavaScript(如JavaScript不是運行中函數中)在window對象的全域範圍下操作,window對象在任何情況下都可以訪問。然而函數中聲明的局部變數只能在該函數中使用。
複製代碼 代碼如下:var animal ='dog';
function getAnimal(adjective) { alert(adjective+''+this.animal); }
getAnimal('lovely'); //彈出 'lovely dog'
這兒我們的變數和函數都聲明在全域範圍中。因為this指向當前範圍,在這個例子中就是window。因此,該函數尋找window.animal,也就是'dog'了。到目前為止,一切正常。然而,實際上,我們可以讓函數運行在不同的範圍下,而忽視其本身的範圍。我們可以用一個內建的稱為call()的方法來實現範圍的冒充。 複製代碼 代碼如下:var animal ='dog';
function getAnimal(adjective) { alert(adjective+''+this.animal); };
var myObj = {animal: 'camel'};
getAnimal.call(myObj, 'lovely'); //彈出 'lovely camel'
call()方法中的第一個參數可以冒充函數中的this,因此,這裡的this.animal實際上就是myObj.animal,也就是'camel'了。後面的參數就作為普通參數傳給函數體。
另外一個與之相關的是apply()方法,其作用於call()一樣,不同之處在於,傳遞給函數的參數是以數組形式表示的,而不是獨立的變數們。所以,上面的測試代碼如果用apply()表示就是: 複製代碼 代碼如下:getAnimal.apply(myObj, ['lovely']); //函數參數以數組形式發送
demo頁面中,點擊第一個按鈕的結果如下:
點擊第二個和第三個按鈕的結果如下:
7. 函數可以執行其本身
下面這個是很OK的: 複製代碼 代碼如下:(function() { alert('hello'); })(); //彈出 'hello'
這裡的解析足夠簡單:聲明一個函數,然後因為()解析立即執行它。你可能會奇怪為何要這麼做(指直接屁股後面()調用),這看上去是有點自相矛盾的:函數包含的通常是我們想稍後執行的代碼,而不是當下解析即執行的,否則,我們就沒有必要把代碼放在函數中。
另外一個執行函數自身(self-executing functions (SEFs))的不錯使用是為在延遲代碼中使用綁定變數值,例如事件的回調(callback),逾時執行(timeouts)和間隔執行(intervals)。如下例子: 複製代碼 代碼如下:var someVar ='hello';
setTimeout(function() { alert(someVar); }, 1000);
var someVar ='goodbye';
Newbies在論壇裡總問這裡timeout的彈出為什麼是goodbye而不是hello?答案就timeout中的回呼函數直到其啟動並執行時候才去賦值someVar變數的值。而那個時候,someVar已經被goodbye重寫了好長時間了。
SEFs提供了一個解決此問題的方法。不是像上面一樣含蓄地指定timeout回調,而是直接將someVar值以參數的形式傳進去。效果顯著,這意味著我們傳入並孤立了someVar值,保護其無論後面是地震海嘯還是女朋友發飆咆哮都不會改變。 複製代碼 代碼如下:var someVar = 'hello';
setTimeout((function(someVar) {
returnfunction() { alert(someVar); }
})(someVar), 1000);
var someVar ='goodbye';
風水輪流轉,這次,這裡的彈出就是hello了。這就是函數參數和外部變數的點差別了哈。
例如,最後一個按鈕點擊後的彈出如下:
瀏覽器
8. FireFox以RGB格式讀與返回顏色而非Hex
直到現在我都沒有真正理解為何Mozilla會這樣子。為了有個清晰的認識,看下面這個例子: 複製代碼 代碼如下:<!--
#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>
大部分瀏覽器彈出的結果是ff9900,而FireFox的結果卻是rgb(255, 153, 0),RGB的形式。經常,處理顏色的時候,我們需要花費不少代碼將RGB顏色轉為Hex。
下面是上面代碼在不同瀏覽器下的結果:
其它雜七雜八
9. 0.1 + 0.2 !== 0.3
這個古怪的問題不只會出現在JavaScript中,這是電腦科學中一個普遍存在的問題,影響了很多的語言。標題等式輸出的結果是0.30000000000000004。
這是個被稱為機器精度的問題。當JavaScript嘗試執行(0.1 + 0.2)這行代碼的時候,會把值轉換成它們喜歡的二進位口味。這就是問題的起源,0.1實際上並不是0.1,而是其二進位形式。從本質上將,當你寫下這些值的時候,它們註定要失去精度。你可能只是希望得到個簡單的兩位小數,但你得到的(根據Chris Pine的註解)是二進位浮點計算。好比你想把一段應該翻譯成中文簡體,結果出來的卻是繁體,其中還是有差異是不一樣的。
一般處理與此相關的問題有兩個做法:
轉換成整數再計算,計算完畢再轉換成希望的小數內容
調整你的邏輯,設定允許範圍為不是指定結果。
例如,我們不應該下面這樣: 複製代碼 代碼如下: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)
我們以一個和風細雨的小古怪結束。聽起來可能有點奇怪,undefined並不是JavaScript中的保留字,儘管它有特殊的意義,並且是唯一的方法確定變數是否未定義。因此: 複製代碼 代碼如下:var someVar;
alert(someVar == undefined); //顯示 true
目前為止,一切看上去風平浪靜,正常無比,但劇情總是很狗血: 複製代碼 代碼如下:undefined ="I'm not undefined!";
var someVar;
alert(someVar == undefined); //顯示 false!
這就是為什麼jQuery源碼中最外部的閉包函數要有個並沒有傳入的undefined參數,目的就是保護undefined不要被外部的些不良乘虛而入。