JavaScript的陷阱【轉載】

來源:互聯網
上載者:User

這本來是翻譯Estelle Weyl的《15 JavaScript Gotchas》,裡面介紹的都是在JavaScript編程實踐中平時容易出錯或需要注意的地方,並提供避開這些陷阱的方法,總體上講,就是在認清事物本質的基礎樣要堅持好的編程習慣,其實這就是Douglas Crockford很久以前提出的JavaScript風格要素問題了,有些內容直接是相同的,具體請看《Javascript風格要素(1)》和《Javascript風格要素(2)》。在翻譯的過程中,我又看到了賢安去年翻譯的《JavaScript的9個陷阱及評點》,其內容又有些交叉在一起,所以我就在現有翻譯的基礎上做了一個簡單的拼合,並依據自己的理解增加了一些注釋和解釋。

  1. 區分大小寫:變數名、屬性和方法全部都區分大小寫
  2. 不匹配的引號、圓括弧或花括弧將拋出錯誤
  3. 條件陳述式:3個常見陷阱
  4. 換行:一直用分號結束語句來避免常見的換行問題
  5. 標點法:在對象聲明的尾部逗號將導致出錯
  6. HTML id 衝突
  7. 變數範圍:全域變數對局部變數
  8. 函數重載:當重載不存在時,覆蓋函數
  9. 區分string.replace()函數不是全域的
  10. parseInt應該包含兩個參數
  11. “this”和綁定問題
  12. 為參數設定預設值,以免你遺漏它們
  13. for each迴圈是用於對象而不是數組
  14. switch語句需要點技巧
  15. 如果你要檢查null,應該先檢查undefined
  16. 時間處理陷阱
區分大小寫

變數名和函數名都是區分大小寫。就像配錯的引號一樣,這些大家都知道。但是,由於錯誤是不作聲的,所以這是一個提醒。為自己選擇一個命名規則,並堅持它。而且,記住JavaScript中的原生函數和CSS屬性都是駱駝拼字法(camelCase)。

getElementById(’myId’) != getElementByID(’myId’); //它應該是“Id”而不是“ID”getElementById(’myId‘) != getElementById(’myID‘); // “Id”也不等於“ID”document.getElementById('myId').style.Color; //返回 "undefined"
不匹配的引號、圓括弧或花括弧

避免陷入不匹配的引號、圓括弧或花括弧陷阱的最好方式是編碼時一直同時寫出開啟和關閉這兩個元素符號,然後在其中間加入代碼。開始:

var myString = ""; //在輸入字串值之前寫入這對引號function myFunction(){ if(){//關閉每個開啟的括弧 }}//統計所有的左括弧和右括弧數量,並且確保它們相等alert(parseInt(var1)*(parseInt(var2)+parseInt(var3))); //關閉每個開啟的圓括弧

每當你開啟一個元素,請關閉它。當你添加了關閉圓括弧後,你再把函數的參數放進圓括弧中。如果有一串圓括弧,統計所有開啟的圓括弧和所有關閉的圓括弧,並且確保這兩個數字相等。

條件陳述式(3個陷阱)
  1. 所有的條件陳述式都必須位於圓括弧中。執行語句主體不管是一句還是多句都強烈建議用花括弧包圍起來,這樣能避免很多因修改或嵌套而產生的潛在錯誤。

    if(var1 == var2){//statement}
  2. 不要犯無意地使用賦值運算子的錯誤:把第二個參數的值賦給第一個參數。因為它是一個邏輯問題,它將一直返回true且不會報錯。
    if(var1 = var2){} // 返回true。把var2賦值給var1
  3. JavaScript是弱類型,除了在switch語句中。當JavaScript在case比較時,它是非弱類型。
    var myVar = 5;if(myVar == '5'){ //返回true,因為JavaScript是弱類型 alert("hi");  //這個alert將執行,因為JavaScript通常不在意資料類型}switch(myVar){ case '5': alert("hi"); //這個alert將不會執行,因為資料類型不匹配}
換行

當心JavaScript中的硬換行。換行被解釋為表示行結束的分號。即使在字串中,如果在引號中包括了一個硬換行,那麼你會得到一個解析錯誤(未結束的字串)。

var bad  = '<ul id="myId">  <li>some text</li>  <li>more text</li>  </ul>'; // 未結束的字串錯誤var good = '<ul id="myId">' + ‘<li>some text</li>‘ + ‘<li>more text</li>‘ + ‘</ul>’; // 正確

前面討論過的換行被解釋為分號的規則並不適用於控制結構這種情況:條件陳述式關閉圓括弧後的換行並不是給其一個分號。

一直使用分號和圓括弧,那麼你不會因換行而出錯,你的代碼易於閱讀,且除了那些不使用分號的怪異源碼外你會少一些顧慮:所以當行動程式碼且最終導致兩個語句在一行時,你無需擔心第一個語句是否正確結束。

多餘的逗號

在任何JavaScript對象定義中,最後一個屬性決不能以一個逗號結尾。Firefox不會出錯,而IE會報語法錯誤。

var theObj = {        city : "Boston",        state : "MA",//IE6和IE7中有“缺少標識符、字串或數字”的錯誤,IE8 beta2修正了它  }
HTML id 衝突

JavaScript DOM綁定(JavaScript DOM bindings)允許通過HTML id索引。在JavaScript中函數和屬性共用同一個名字空間。所以,當在HTML中的一個id和函數或屬性有相同的名字時,你會得到難以跟蹤的邏輯錯誤。然而這更多是一個CSS最佳實務的問題,當你不能解決你的JavaScript問題時,想起它是很重要的。

<ul><li id="length">1</li><li id="thisLength">2</li><li id="thatLength">3</li></ul><script>var listitems = document.getElementsByTagName('li');var liCount = listitems.length; //IE下返回的是<li id="length">1</li>這個節點而不是所有<li的數量var thisLength = document.getElementById('thisLength');    thatLength = document.getElementById('thatLength');    //IE下會出現“對象不支援此屬性和方法”的錯誤,IE8 beta2下首次載入頁面會出錯,重新整理頁面則不會    //在IE中thisLength和thatLength直接表示以其為id值的DOM節點,    //所以賦值時會出錯,當有var聲明時,IE會把其當著變數,這個時候就正常了。</script>

如果你要標記(X)HTML,絕不要使用JavaScript方法或屬性名稱作為id的值。並且,當你寫JavaScript時,避免使用 (X)HTML中的id值作為變數名。

變數範圍

JavaScript中的許多問題都來自於變數範圍:要麼認為局部變數是全域的,要麼用函數中的局部變數覆蓋了全域變數。為了避免這些問題,最佳方案是根本沒有任何全域變數。但是,如果你有一堆,那麼你應該知道這些陷阱。

不用var關鍵字聲明的變數是全域的。記住使用var關鍵字聲明變數,防止變數具有全域範圍。在下面例子中,在函數中聲明的變數具有全域變數,因為沒有使用var關鍵字聲明:

anonymousFuntion1 = function(){ globalvar = 'global scope'; //全域聲明,因為“var”遺漏了 return localvar;}();alert(globalvar); //彈出“global scope”,因為函數中的變數是全域聲明anonymousFuntion2 = function(){ var localvar = 'local scope'; //使用“var”局部聲明 return localvar;}();alert(localvar); //錯誤 “localvar未定義”。沒有全域定義localvar

作為參數引進到函數的變數名是局部的。如果參數名也是一個全域變數的名字,像參數變數一樣有局部範圍,這沒有衝突。如果你想在函數中改變一個全域變數,這個函數有一個參數複製於這個全域變數名,記住所有全域變臉都是window對象的屬性。

var myscope = "global";function showScope(myscope){ return myscope; //局部範圍,即使有一個相同名字的全域變數}alert(showScope('local'));function globalScope(myscope){ myscope = window.myscope; //全域範圍 return myscope;}alert(globalScope(’local’));

你甚至可以在迴圈中聲明變數:

for(var i = 0; i < myarray.length; i++){}
覆蓋函數/重載函數

當你不止一次的聲明一個函數時,這個函數的最後一次聲明將覆蓋掉該函數的所有前面版本且不會拋出任何錯誤或警告。這不同於其他的程式設計語言,像Java,你能用相同的名字有多重函數,只要它們有不同的參數:調用函數重載。在JavaScript中沒有重載。這使得不能在代碼中使用JavaScript核心部分的名字極其重要。也要當心包含的多個JavaScript檔案,像一個包含的指令檔可能覆蓋另一個指令檔中的函數。請使用匿名函數和名字空間。

(function(){ // creation of my namespace 建立我的名字空間 if(!window.MYNAMESPACE) {  window['MYNAMESPACE'] = {}; } //如果名字空間不存在,就建立它 //這個函數僅能在匿名函數中訪問 function myFunction(var1, var2){  //內部的函數代碼在這兒 } // 把內建函式串連到名字空間上,使它通過使用名字空間能訪問匿名函數的外面  window['MYNAMESPACE']['myFunction'] = myFunction;})(); // 圓括弧 = 立即執行// 包含所有代碼的圓括弧使函數匿名

這個例子正式為了實現解決上一個陷阱“變數範圍”的最佳方案。匿名函數詳細內容請看《Javascript的匿名函數》。YUI整個庫只有YAHOO和YAHOO_config兩個全域變數,它正是大量應用匿名函數和命名空間的方法來實現,具體請看《Javascript的一種模組模式》。

字串替換

一個常見錯誤是假設字串替換方法的行為會對所有可能匹配都產生影響。實際上,JavaScript字串替換隻改變了第一次發生的地方。為了替換所有發生的地方,你需要設定全域標識。同時需要記住String.replace()的第一個參數是一個Regex。

var myString = "this is my string";myString = myString.replace("","%20"); // "this%20is my string"myString = myString.replace(/ /,"%20"); // "this%20is my string"myString = myString.replace(/ /g,"%20"); // "this%20is%20my%20string"
parseInt

在JavaScript得到整數的最常見錯誤是假設parseInt返回的整數是基於10進位的。別忘記第二個參數基數,它能是從2到36之間的任何值。為了確保你不會弄錯,請一直包含第二個參數。

parseInt('09/10/08'); //0parseInt(’09/10/08′,10); //9, 它最可能是你想從一個日期中得到的值

如果parseInt沒有提供第二個參數,則首碼為 ‘0x’ 的字串被當作十六進位,首碼為 ‘0′ 的字串被當作八進位。所有其它字串都被當作是十進位的。如果 numString 的首碼不能解釋為整數,則返回 NaN(而不是數字)。

‘this’

另一個常見的錯誤是忘記使用“this”。在JavaScript對象中定義的函數訪問這個對象的屬性,但沒有使用引用標識符“this”。例如,下面是錯誤的:

function myFunction() { var myObject = {  objProperty: "some text",  objMethod: function() {   alert(objProperty);  } }; myObject.objMethod();} function myFunction() { var myObject = {  objProperty: "some text",  objMethod: function() {   alert(this.objProperty);  } }; myObject.objMethod();}

有一篇A List Apart文章用通俗易懂的英文表達了this綁定的問題。

對this使用最大的陷阱是this在使用過程中其引用會發生改變:

<input type="button" value="Gotcha!" id="MyButton"><script>var MyObject = function () {    this.alertMessage = "Javascript rules";    this.ClickHandler = function() {        alert(this.alertMessage );        //返回結果不是”JavaScript rules”,執行MyObject.ClickHandler時,        //this的引用實際上指向的是document.getElementById("theText")的引用  }}();document.getElementById(”theText”).onclick =  MyObject.ClickHandler</script>

其解決方案是:

var MyObject = function () {    var self = this;    this.alertMessage = “Javascript rules”;    this.OnClick = function() {        alert(self.value);    }}();

類似問題的更多細節和解決方案請看《JavaScript範圍的問題》。

遺漏的參數

當給函數增加一個參數時,一個常見的錯誤是忘記更新這個函數的所有調用。如果你需要在已經被調用的函數中增加一個參數來處理一個特殊情況下的調用,請給這個函數中的這個參數設定預設值,以防萬一在眾多指令碼中的眾多調用中的一個忘記更新。

function addressFunction(address, city, state, country){ country = country || “US”; //如果沒有傳入country,假設 “US” span>//剩下代碼}

你也能通過擷取arguments來解決。但是在這篇文章我們的注意力在陷阱上。同時在《Javascript風格要素(2)》也介紹了||巧妙應用。

for關鍵字

在JavaScript中關鍵字for有兩種使用方式,一個是for語句,一個是for/in語句。for/in語句將遍曆所有的對象屬性(attribute),包括方法和屬性(property)。決不能使用for/in來遍曆數組:僅在當需要遍曆對象屬性和方法時才使用for/in。

  1. for(var myVar in myObject)語句用一個指定變數無任何規律地遍曆對象的所有屬性。如果for/in迴圈的主體刪除了一個還沒有枚舉出的屬性,那麼該屬性就不在枚舉。如果迴圈主體定義了新屬性,那麼迴圈是否枚舉該屬性則是由JavaScript的實現決定。
  2. for(var 1=0; i < myArray.length; i++)語句會遍曆完一個數組的所有元素。

為瞭解決這個問題,大體上你可以對對象使用 for … in,對數組使用for迴圈:

listItems = document.getElementsByTagName('li');for (var listitem in listItems){ //這裡將遍曆這個對象的所有屬性和方法,包括原生的方法和屬性,但不遍曆這個數組:出錯了!}//因為你要迴圈的是數組對象,所用for迴圈for ( var i = 0; i < listItems.length; i++) { //這是真正你想要的}

對象的有些屬性以相同的方式標記成唯讀、永久的或不可列舉的,這些屬性for/in無法枚舉。實際上,for/in迴圈
會遍曆所有對象的所有可能屬性,包括函數和原型中的屬性。所有修改原型屬性可能對for/in迴圈帶來致命的危害,所以需要採用hasOwnProperty和typeof做一些必要的過濾,最好是用for來代替for/in。

switch語句

Estelle Weyl寫了一篇switch statement quirks,其要點是:

  1. 沒有資料類型轉換
  2. 一個匹配,所有的運算式都將執行直到後面的break或return語句執行
  3. 你可以對一個單獨語句塊使用多個case從句
undefined ≠ null

null是一個對象,undefined是一個屬性、方法或變數。存在null是因為對象被定義。如果對象沒有被定義,而測試它是否是null,但因為沒有被定義,它無法測試到,而且會拋出錯誤。

if(myObject !== null  && typeof(myObject) !== 'undefined') { //如果myObject是undefined,它不能測試是否為null,而且還會拋出錯誤}if(typeof(myObject) !== 'undefined' && myObject !== null) { //處理myObject的代碼}

Harish Mallipeddi對undefined和null有一個說明。

事件處理陷阱

剛接觸事件處理時最常見的寫法就是類似:

window.onclick = MyOnClickMethod

這種做法不僅非常容易出現後面的window.onclick事件覆蓋掉前面的事件,還可能導致大名頂頂的IE記憶體泄露問題。為瞭解決類似問題,4年前Simon Willison就寫出了很流行的addLoadEvent():

function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') {  window.onload = func; }else {  window.onload = function() {   oldonload();   unc();  } }}addEvent(window,'load',func1,false);addEvent(window,'load',func2,false);addEvent(window,'load',func3,false);

當然在JavaScript庫盛行的現在,使用封裝好的事件處理機制是一個很好的選擇,比如在YUI中就可以這樣寫:

YAHOO.util.Event.addListener(window, "click", MyOnClickMethod);

原文地址:http://dancewithnet.com/2008/10/27/javascript-gotchas/comment-page-1/#comment-635008

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.