Javascript中的陷阱大集合【譯】

英文原文:A Collection of JavaScript Gotchas
譯文做者:王國峯
譯文標題:Javascript中的陷阱大集合【譯】
譯文連接:http://www.itivy.com/ivy/archive/2011/11/13/my- javascript-gotchas.html
來源:http://www.cnblogs.com/sxwgf/archive/2011/11/14/javascript-gotchas.html

函數和操做符
雙等號
==操做符比較時會進行類型的強制轉換,這意味着它能夠比較兩個不一樣類型的對象,在執行比較以前它將會嘗試把這兩個對象轉換成同一個類型,舉一個例子:
"1" == 1 //true
然而,這樣每每會誤導咱們,並且咱們也不須要這樣子來比較。在上面的例子中,咱們徹底能夠先將字符串轉換成數字型,而後利用對類型敏感的三重等號(===)來進行比較,如:
Number("1") === 1; //true
或者,更好的是,確保你放在首位的操做數的類型是正確的。
因爲雙等號具備強制類型轉換的行爲,因此它會打破通常的傳遞性規則,這點有點嚇人,請看下面的列子:
"" == 0 //true - 空字符串會被強制轉換爲數字0.
0 == "0" //true - 數字0會被強制轉換成字符串"0"
"" == "0" //false - 兩操做數都是字符串因此不執行強制轉換
若是使用三重等號,上面的三個比較都將返回false。
parseInt不把10做爲數字基數
若是你忽略parseInt的第二個參數,那麼數字的基數將由下面的規則所決定:
默認基數爲10,即按10進制解析
若是數字以0x開頭,那麼基數爲16,即按16進制解析
若是數字以0開頭,那麼基數爲8,即按8進制解析
一個常見的錯誤是咱們讓用戶輸入以0開頭的數字,這時候它就按8進制的方式去解析了,因而咱們就看到了以下的效果:
parseInt("8"); //8
parseInt("08"); //0
所以,咱們不少時候都會指定parseInt的第二個參數,以下所示:
parseInt("8", 10); //8
parseInt("08", 10); //8
ECMAScript5方面的說明:ECMAScript已再也不支持8進制的解析假設,另外,若是忽略parseInt的第二個參數將會引發JSLint的警告。
字符串替換
字符串替換函數僅僅會替換第一個匹配項,並不能替換你所指望的所有匹配項。以下代碼:
"bob".replace("b", "x"); // "xob"
"bob".replace(/b/, "x"); // "xob" (使用了正則表達式)
若是要替換全部的匹配項,咱們可使用正則表達式,併爲他它添加全局修飾符,以下代碼:
"bob".replace(/b/g, "x"); // "xox"
"bob".replace(new RegExp("b", "g"), "x"); // "xox" (alternate explicit RegExp)
全局修飾符確保了替換函數找到第一個匹配項後不會中止對下一個匹配項的替換。
「+"操做符會執行相加操做和字符串鏈接操做
php做爲另外一種弱類型語言,可使用」.「操做符對字符串進行鏈接。Javascript卻不是這樣的 - 因此當操做數是字符串的時候」a+b「一般是執行鏈接操做。若是你想執行數字相加那你就要引發注意了,由於輸入的內容多是字符串類型的,因此你在執行相 加操做前須要先將其轉換成數字類型,代碼以下:
1 + document.getElementById("inputElem").value; // 鏈接操做
1 + Number(document.getElementById("inputElem").value); // 相加操做
須要注意的是,相減操做會嘗試將操做數轉換成數字類型,代碼以下:
"3" - "1"; // 2
儘管有時候你想用減法將字符串從另外一個字符串中減掉,但這時候每每會產生一些邏輯錯誤。
不少時候咱們用數字和空串相加來實現數字轉換成字符串的操做,代碼以下:
3 + ""; // "3"
可是這樣作並很差,因此咱們能夠用String(3)來取代上面的方法。
typeof
typeof這會返回一個javascript基本類型的實例的類型。Array實際上不是基本類型,因此typeof Array對象將返回Object,代碼以下:
typeof {} === "object" //true
typeof "" === "string" //true
typeof [] === "array"; //false
當你對本身的對象的實例使用這個操做符時將會獲得相同的結果(typeof = "object")。
另外說明一點,」typeof null「將返回」object「,這個有點詭異。
instanceof
instanceof返回指定對象是不是由某個類構造的實例,這個對咱們檢查指定對象是不是自定義類型之一頗有幫助,可是,若是你是用文本語法建立的內置類型那可能會得出錯誤的結果,代碼以下:
"hello" instanceof String; //false
new String("hello") instanceof String; //true
因爲Array實際上不是內置類型(只是假裝成內置類型 - 所以對它使用typeof不能獲得預期的結果),可是使用instanceof就能獲得預期效果了,代碼以下所示:
["item1", "item2"] instanceof Array;  //true
new Array("item1", "item2") instanceof Array;  //true
唉,不爽!總的來講,若是你想測試Boolean, String, Number, 或者Function的類型,你可使用typeof,對於其餘的任何類型,你可使用instanceof測試。
哦,還有一點,在一個function中,有一個預約義變量叫「arguments」,它以一個array的形式傳遞給function。然而,它並非真正的array,它只是一個相似array的對象,帶有長度屬性而且屬性值從0-length。很是奇怪...你能夠用下面的小伎倆將它轉換成真正的數組:
var args = Array.prototype.slice.call(arguments, 0);
這個對由getElementsByTagName返回的NodeList對象也是同樣的 - 它們均可以用以上的代碼轉換成合適的數組。
eval
eval 能夠將字符串以javascript代碼的形式來解析執行,可是通常來講咱們不建議這麼作。由於eval很是慢 - 當javascript被加載到瀏覽器中時,它會被編譯成本地代碼;然而執行的過程當中每次遇到eval表達式,編譯引擎都將從新啓動執行編譯,這樣作的代 價太大了。並且這樣作也醜陋無比,有不少eval被濫用的例子。另外,在eval中的代碼會在當前範圍內執行,所以它能夠修改局部變量,以及在你的範圍內 添加一些讓你意想不到的東西。
JSON 轉換是咱們常常要作的;一般咱們使用「var obj = eval(jsonText);」來進行轉換。然而如今幾乎全部的瀏覽器都支持本地JSON對象,你可使用「var obj = JSON.parse(jsonText);」來替代前面的代碼。相反你也能夠用「JSON.stringify」將JSON對象轉換成字符串。更妙的 是,你可使用「jQuery.parseJSON」來完成上述的工做。
setTimeout和setInterval函數的第一個參數能夠用字符串做爲函數體來解析執行,固然,咱們也不建議這樣作,咱們能夠用實際的函數來替代。
最後,Function的構造函數和eval很是像,惟一不一樣的是,Function構造函數是在全局範圍內執行的。
with
with表達式將爲你提供訪問對象屬性的速記方式,但咱們是否應該使用它,仍然存在矛盾的觀點。Douglas Crockford不太喜歡它。John Resig在他的書中有找了不少with的巧妙用法,可是他也認可這將會影響性能而且會產生一點混亂。來看看咱們分離出來的with代碼塊,他不能準確地告訴咱們如今正在執行什麼,代碼以下所示:
with (obj) {
    bob = "mmm";
    eric = 123;
}
我是否剛剛修改了一個叫bob的局部變量?或者我是否設置了obj.bob?若是obj.bob已經被定義,那麼它將會被重置爲「mmm」。不然,若是有 另外一個bob在這個範圍中,那麼他將會被改變。不然,全局變量bob會被設置。最後,下面的寫法能夠很是明確地表達你的意思:
obj.bob = "mmm";
obj.eric = 123;
ECMAScript5說明:ES5嚴格的來講已經不支持with表達式。
類型和構造函數
使用 「new」關鍵字構造內置類型
Javascript中有Object, Array, Boolean, Number, String, 和Function這些類型,他們各自都有各自的文字語法,因此就不須要顯式構造函數了。
顯式構造(不建議) 文字語法(推薦)
var a = new Object();
a.greet = "hello"; var a = { greet: "hello" };
var b = new Boolean(true); var b = true;
var c = new Array("one", "two"); var c = ["one", "two"];
var d = new String("hello"); var d = "hello"
var e = new Function("greeting", "alert(greeting);"); var e = function(greeting) { alert(greeting); };
然而,若是你使用new關鍵字來構造上面其中的一種類型,你實際上將會獲得一個類型爲Object而且繼承自你要構造的類型的原型的對象(Function類型除外)。因此儘管你用new關鍵字構造了一個Number類型,它也將是一個Object類型,以下代碼:
typeof new Number(123); // "object"
typeof Number(123); // "number"
typeof 123; // "number"
上面的第三項是文本語法,爲了不衝突,咱們應該使用這種方法來構造上面的這些類型。
使用「new」關鍵字來構造任何東西
若是你自寫構造函數而且忘記了new關鍵字,那麼悲劇就發生了:
var Car = function(colour) {
    this.colour = colour;
};
 
var aCar = new Car("blue");
console.log(aCar.colour); // "blue"
 
var bCar = Car("blue");
console.log(bCar.colour); // error
console.log(window.colour); //"blue"
使用new關鍵字調用函數會建立一個新的對象,而後調用新對象上下文中的函數,最後再返回該對象。相反的,若是不使用new關鍵在調用函數,那它將會變成一個全局對象。
偶然忘記使用new關鍵字意味着不少可選擇的對象構造模式已經出現能夠徹底刪除使用這個關鍵字的需求的狀況,儘管這超出了本文的範圍,但我仍是建議你去進一步閱讀。
沒有Integer類型
數值計算是相對緩慢的,由於沒有Integer類型。只有Number類型 - Number是IEEE標準中雙精度浮點運算(64位)類型。這就意味着Number會引發下面的精度舍入錯誤:
0.1 + 0.2 === 0.3 //false
由於integers和floats沒有區別,不像C#和JAVA下面代碼是true:
0.0 === 0; //true
最後是一個關於Number的疑問,咱們該如何實現下面的問題:
a === b; //true
1/a === 1/b; //false
答案是按照Number的規範是容許出現+0和-0的,+0等於-0,可是正無窮大不等於負無窮大,代碼以下:
var a = 0 * 1; // 這個結果爲0
var b = 0 * -1; // 這個結果爲-0 (你也能夠直接"b=-0",可是你爲什麼要這樣作?)
a === b; //true: 0等於-0
1/a === 1/b; //false: 正無窮大不等於負無窮大
做用域
沒有塊做用域
由於你可能已經注意到上一個觀點,javascript中沒有塊做用域的概念,只有函數做用域。能夠試試下面的代碼:
for(var i=0; i<10; i++) {
    console.log(i);
}
var i;
console.log(i); // 10
當i被定義在for循環中,退出循環後它人被保留在這個做用域內,因此最後調用console.log輸出了10。這裏有一個JSLint警告來讓你避免這個問題:強制將全部的變量定義在函數的開頭。 咱們有可能經過寫一個當即執行的function來建立一個做用域:
(function (){
    for(var i=0; i<10; i++) {
        console.log(i);
    }
}());
var i;
console.log(i); // undefined
當你在內部函數以前聲明一個變量,而後在函數裏重聲明這個變量,那將會出現一個奇怪的問題,示例代碼以下:
var x = 3;
(function (){
    console.log(x + 2); // 5
    x = 0; //No var declaration
}());
可是,若是你在內部函數中從新聲明x變量,會出現一個奇怪的問題:
var x = 3;
(function (){
    console.log(x + 2); //NaN - x is not defined
    var x = 0; //var declaration
}());
這是由於在函數中x變量被從新定義了,這說明了翻譯程序將var表達式移動到了函數頂部了,最終就變成這樣執行了:
var x = 3;
(function (){
    var x;
    console.log(x + 2); //NaN - x is not defined
    x = 0;
}());
這個實在是太有意義了!
全局變量
Javascript 有一個全局做用域,在爲你的代碼建立命名空間時必定要當心謹慎。全局變量會給你的應用增長一些性能問題,由於當你訪問它們時,運行時不得不經過每個做用 域來創建知道找到它們爲止。他們會因你的有意或者無心而被訪問或者修改,這將致使另一個更加嚴重的問題 - 跨站點腳本攻擊。若是一個不懷好意的傢伙在你的頁面上找出瞭如何執行那些代碼的方法,那麼他們就能夠經過修改全局變量很是容易地擾亂你的應用。缺少經驗的 開發者在無心中會不斷的將變量添加到全局做用域中,經過本文,將會告訴你們這樣會發生什麼意外的事情。
我曾經看到過下面的代碼,它將嘗試聲明兩個值相等的局部變量:
var a = b = 3;
這樣很是正確的獲得了a=3和b=3,可是a在局部做用域中而b在全局做用域中,」b=3「將會被先執行,全局操做的結果,3,再被分配給局部變量a。
下面的代碼聲明瞭兩個值爲3的變量,這樣能達到預期的效果:
var a = 3,
b = a;
」this「和內部函數
」this「關鍵字一般指當前正在執行的函數所在的對象,然而,若是函數並無在對象上被調用,好比在內部函數中,」this「就被設置爲全局對象(window),以下代碼:
var obj = {
    doSomething: function () {
        var a = "bob";
        console.log(this); // 當前執行的對象
        (function () {
            console.log(this); // window - "this" is reset
            console.log(a); // "bob" - still in scope
        }());
    }
};
obj.doSomething();
雜項
數據不存在:」null「和」undefined「
有兩種對象狀態來代表數據不存在:null和undefined。這會讓那些從其餘編程語言好比C#轉過來的程序員變得至關混亂。也許你會指望下面的代碼返回true:
var a;
a === null; //false
a === undefined; //true
」a「其實是undefined的(儘管你用雙等號==來與null比較會得出true的結果,但這只是表面上看起來正確的另外一個錯誤)。
若是你想檢查一個變量是否真的存在值,那你不能用雙等號==去判斷,要用下面的方法:
if(a !== null && a !== undefined) {
    ...
}
」哈「,你也許會說,既然null和undefined都是false,那麼你能夠這樣去作:
if(a) {
    ...
}
固然,0是false,空字符串也是。那麼若是這其中一個是a的正確的值的話,你就要用前者了。那種比較短小的比較方式,適合於比較objects, arrays, 和booleans類型。
重定義undefined
很是正確,你能夠重定義undefined,由於它不是一個保留字:
undefined = "surprise!";
可是,你要經過給undefined變量分配一個值或者使用」void「操做符來取回值(不然這是至關沒用的)。
undefined = void 0;
這就是爲何 jquery腳本庫的第一行要這樣寫了:
(function ( window, undefined ) {
    ... // jQuery library!
}(window));
這個函數被調用時是傳入一個參數的,同時確保了第二個參數」undefined「其實是undefined的。
順便說一下,你不能重定義null - 可是你能夠重定義NaN,Infinity和帶構造函數的內置類型。能夠這樣嘗試一下:
Array = function (){ alert("hello!"); }
var a = new Array();
固然,你能夠在任何地方用文字語法聲明Array。
可選的分號
Javascript代碼中分號是可選的,因此初學者寫代碼就簡單多了。可是很不幸的是若是忽略了分號並不會給任何人帶來方便。結果是當解釋器遇到錯誤時,必須追溯並嘗試去猜想由於哪些分號漏寫致使的問題。
這裏有一個經典的例子:
return
{
    a: "hello"
};
上面的代碼並不會返回一個對象,而是返回了undefined - 可是也沒有錯誤拋出。實際上是由於分號自動加到了return語句後面,其餘的代碼都是很是正確的,可是就是什麼都不執行,這就證實了在 javascript中,左花括號應該緊跟這一行而不應換行,這不僅是一個編程風格的問題。下面的代碼纔會正確返回一個屬性爲a的對象:
r eturn {
    a: "hello"
};
NaN
NaN的類型是...Number
typeof NaN === "number" //true
另外NaN和任何東西比較都是false:
NaN === NaN; // false
由於NaN之間是不能比較的,惟一判斷一個數字是否爲NaN的方法是調用isNaN方法。
從另外一個方面能夠說明,咱們也能夠用函數isFinite,當其中一個操做數爲NaN或者InFinity時返回false。
arguments對象
在一個函數中,咱們能夠引用arguments對象來遍歷傳入的參數列表,第一個比較怪異的地方是這個對象並非Array,而是一個相似 Array的對象(有一個length屬性,其值在0-length-1之間)。爲了將其轉換成array,咱們能夠array的splice函數來建立 其對應的array數組:
(function(){
console.log(arguments instanceof Array); // false
var argsArray = Array.prototype.slice.call(arguments);
console.log(argsArray instanceof Array); // true
}());
第二個比較怪異的地方是當一個函數的簽名中有顯式arguments參數時,它們是能夠被從新分配的而且arguments對象也會被改變。這就代表了arguments對象指向了變量自己。你不能利用arguments對象來給出它們的初始值:
(function(a){
    alert(arguments[0]); //1
    a = 2;
    alert(arguments[0]); //2
}(1));
結束本文!
相關文章
相關標籤/搜索