版權(quán)聲明:轉(zhuǎn)載時(shí)請(qǐng)以超鏈接形式標(biāo)明文章原始出處和作者信息及本聲明
http://monw3c.blogbus.com/logs/31056019.html
這本來(lái)是翻譯Estelle Weyl的《15 JavaScript Gotchas》,里面介紹的都是在JavaScript編程實(shí)踐中平時(shí)容易出錯(cuò)或需要注意的地方,并提供避開這些陷阱的方法,總體上講,就是在認(rèn)清事物本質(zhì)的基礎(chǔ)樣要堅(jiān)持好的編程習(xí)慣,其實(shí)這就是Douglas Crockford很久以前提出的JavaScript風(fēng)格要素問(wèn)題了,有些內(nèi)容直接是相同的,具體請(qǐng)看《Javascript風(fēng)格要素(1)》和《Javascript風(fēng)格要素(2)》。在翻譯的過(guò)程中,我又看到了賢安去年翻譯的《JavaScript的9個(gè)陷阱及評(píng)點(diǎn)》,其內(nèi)容又有些交叉在一起,所以我就在現(xiàn)有翻譯的基礎(chǔ)上做了一個(gè)簡(jiǎn)單的拼合,并依據(jù)自己的理解增加了一些注釋和解釋。
變量名和函數(shù)名都是區(qū)分大小寫的。就像配錯(cuò)的引號(hào)一樣,這些大家都知道。但是,由于錯(cuò)誤是不作聲的,所以這是一個(gè)提醒。為自己選擇一個(gè)命名規(guī)則,并堅(jiān)持它。而且,記住JavaScript中的原生函數(shù)和CSS屬性都是駱駝拼寫法(camelCase)。
getElementById(’myId’) != getElementByID(’myId’); //它應(yīng)該是“Id”而不是“ID”getElementById(’myId‘) != getElementById(’myID‘); // “Id”也不等于“ID”document.getElementById('myId').style.Color; //返回 "undefined"
避免陷入不匹配的引號(hào)、圓括號(hào)或花括號(hào)陷阱的最好方式是編碼時(shí)一直同時(shí)寫出打開和關(guān)閉這兩個(gè)元素符號(hào),然后在其中間加入代碼。開始:
var myString = ""; //在輸入字符串值之前寫入這對(duì)引號(hào)function myFunction(){if(){//關(guān)閉每個(gè)打開的括弧}}//統(tǒng)計(jì)所有的左括號(hào)和右括號(hào)數(shù)量,并且確保它們相等alert(parseInt(var1)*(parseInt(var2)+parseInt(var3))); //關(guān)閉每個(gè)打開的圓括號(hào)
每當(dāng)你打開一個(gè)元素,請(qǐng)關(guān)閉它。當(dāng)你添加了關(guān)閉圓括號(hào)后,你再把函數(shù)的參數(shù)放進(jìn)圓括號(hào)中。如果有一串圓括號(hào),統(tǒng)計(jì)所有打開的圓括號(hào)和所有關(guān)閉的圓括號(hào),并且確保這兩個(gè)數(shù)字相等。
if(var1 == var2){//statement}
if(var1 = var2){} // 返回true。把var2賦值給var1
var myVar = 5; if(myVar == '5'){ //返回true,因?yàn)镴avaScript是弱類型 alert("hi"); //這個(gè)alert將執(zhí)行,因?yàn)镴avaScript通常不在意數(shù)據(jù)類型 } switch(myVar){ case '5': alert("hi"); //這個(gè)alert將不會(huì)執(zhí)行,因?yàn)閿?shù)據(jù)類型不匹配 }
當(dāng)心JavaScript中的硬換行。換行被解釋為表示行結(jié)束的分號(hào)。即使在字符串中,如果在引號(hào)中包括了一個(gè)硬換行,那么你會(huì)得到一個(gè)解析錯(cuò)誤(未結(jié)束的字符串)。
var bad = '<ul id="myId"><li>some text</li><li>more text</li></ul>'; // 未結(jié)束的字符串錯(cuò)誤var good = '<ul id="myId">' +‘<li>some text</li>‘ +‘<li>more text</li>‘ +‘</ul>’; // 正確
前面討論過(guò)的換行被解釋為分號(hào)的規(guī)則并不適用于控制結(jié)構(gòu)這種情況:條件語(yǔ)句關(guān)閉圓括號(hào)后的換行并不是給其一個(gè)分號(hào)。
一直使用分號(hào)和圓括號(hào),那么你不會(huì)因換行而出錯(cuò),你的代碼易于閱讀,且除了那些不使用分號(hào)的怪異源碼外你會(huì)少一些顧慮:所以當(dāng)移動(dòng)代碼且最終導(dǎo)致兩個(gè)語(yǔ)句在一行時(shí),你無(wú)需擔(dān)心第一個(gè)語(yǔ)句是否正確結(jié)束。
在任何JavaScript對(duì)象定義中,最后一個(gè)屬性決不能以一個(gè)逗號(hào)結(jié)尾。Firefox不會(huì)出錯(cuò),而IE會(huì)報(bào)語(yǔ)法錯(cuò)誤。
var theObj = {city : "Boston",state : "MA",//IE6和IE7中有“缺少標(biāo)識(shí)符、字符串或數(shù)字”的錯(cuò)誤,IE8 beta2修正了它}
JavaScript DOM綁定(JavaScript DOM bindings)允許通過(guò)HTML id索引。在JavaScript中函數(shù)和屬性共享同一個(gè)名字空間。所以,當(dāng)在HTML中的一個(gè)id和函數(shù)或?qū)傩杂邢嗤拿謺r(shí),你會(huì)得到難以跟蹤的邏輯錯(cuò)誤。然而這更多是一個(gè)CSS最佳實(shí)踐的問(wèn)題,當(dāng)你不能解決你的JavaScript問(wèn)題時(shí),想起它是很重要的。
<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>這個(gè)節(jié)點(diǎn)而不是所有<li的數(shù)量var thisLength = document.getElementById('thisLength');thatLength = document.getElementById('thatLength');//IE下會(huì)出現(xiàn)“對(duì)象不支持此屬性和方法”的錯(cuò)誤,IE8 beta2下首次加載頁(yè)面會(huì)出錯(cuò),刷新頁(yè)面則不會(huì)//在IE中thisLength和thatLength直接表示以其為id值的DOM節(jié)點(diǎn),//所以賦值時(shí)會(huì)出錯(cuò),當(dāng)有var聲明時(shí),IE會(huì)把其當(dāng)著變量,這個(gè)時(shí)候就正常了。</script>
如果你要標(biāo)記(X)HTML,絕不要使用JavaScript方法或?qū)傩悦鳛閕d的值。并且,當(dāng)你寫JavaScript時(shí),避免使用 (X)HTML中的id值作為變量名。
JavaScript中的許多問(wèn)題都來(lái)自于變量作用域:要么認(rèn)為局部變量是全局的,要么用函數(shù)中的局部變量覆蓋了全局變量。為了避免這些問(wèn)題,最佳方案是根本沒有任何全局變量。但是,如果你有一堆,那么你應(yīng)該知道這些陷阱。
不用var關(guān)鍵字聲明的變量是全局的。記住使用var關(guān)鍵字聲明變量,防止變量具有全局作用域。在下面例子中,在函數(shù)中聲明的變量具有全局變量,因?yàn)闆]有使用var關(guān)鍵字聲明:
anonymousFuntion1 = function(){globalvar = 'global scope'; //全局聲明,因?yàn)?#8220;var”遺漏了return localvar;}();alert(globalvar); //彈出“global scope”,因?yàn)楹瘮?shù)中的變量是全局聲明anonymousFuntion2 = function(){var localvar = 'local scope'; //使用“var”局部聲明return localvar;}();alert(localvar); //錯(cuò)誤 “localvar未定義”。沒有全局定義localvar
作為參數(shù)引進(jìn)到函數(shù)的變量名是局部的。如果參數(shù)名也是一個(gè)全局變量的名字,像參數(shù)變量一樣有局部作用域,這沒有沖突。如果你想在函數(shù)中改變一個(gè)全局變量,這個(gè)函數(shù)有一個(gè)參數(shù)復(fù)制于這個(gè)全局變量名,記住所有全局變臉都是window對(duì)象的屬性。
var myscope = "global";function showScope(myscope){return myscope; //局部作用域,即使有一個(gè)相同名字的全局變量}alert(showScope('local'));function globalScope(myscope){myscope = window.myscope; //全局作用域return myscope;}alert(globalScope(’local’));
你甚至可以在循環(huán)中聲明變量:
for(var i = 0; i < myarray.length; i++){}
當(dāng)你不止一次的聲明一個(gè)函數(shù)時(shí),這個(gè)函數(shù)的最后一次聲明將覆蓋掉該函數(shù)的所有前面版本且不會(huì)拋出任何錯(cuò)誤或警告。這不同于其他的編程語(yǔ)言,像Java,你能用相同的名字有多重函數(shù),只要它們有不同的參數(shù):調(diào)用函數(shù)重載。在JavaScript中沒有重載。這使得不能在代碼中使用JavaScript核心部分的名字極其重要。也要當(dāng)心包含的多個(gè)JavaScript文件,像一個(gè)包含的腳本文件可能覆蓋另一個(gè)腳本文件中的函數(shù)。請(qǐng)使用匿名函數(shù)和名字空間。
(function(){// creation of my namespace 創(chuàng)建我的名字空間if(!window.MYNAMESPACE) {window['MYNAMESPACE'] = {};}//如果名字空間不存在,就創(chuàng)建它//這個(gè)函數(shù)僅能在匿名函數(shù)中訪問(wèn)function myFunction(var1, var2){//內(nèi)部的函數(shù)代碼在這兒}// 把內(nèi)部函數(shù)連接到名字空間上,使它通過(guò)使用名字空間能訪問(wèn)匿名函數(shù)的外面 window['MYNAMESPACE']['myFunction'] = myFunction;})(); // 圓括號(hào) = 立即執(zhí)行// 包含所有代碼的圓括號(hào)使函數(shù)匿名
這個(gè)例子正式為了實(shí)現(xiàn)解決上一個(gè)陷阱“變量作用域”的最佳方案。匿名函數(shù)詳細(xì)內(nèi)容請(qǐng)看《Javascript的匿名函數(shù)》。YUI整個(gè)庫(kù)只有YAHOO和YAHOO_config兩個(gè)全局變量,它正是大量應(yīng)用匿名函數(shù)和命名空間的方法來(lái)實(shí)現(xiàn),具體請(qǐng)看《Javascript的一種模塊模式》。
一個(gè)常見錯(cuò)誤是假設(shè)字符串替換方法的行為會(huì)對(duì)所有可能匹配都產(chǎn)生影響。實(shí)際上,JavaScript字符串替換只改變了第一次發(fā)生的地方。為了替換所有發(fā)生的地方,你需要設(shè)置全局標(biāo)識(shí)。同時(shí)需要記住String.replace()的第一個(gè)參數(shù)是一個(gè)正則表達(dá)式。
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"
在JavaScript得到整數(shù)的最常見錯(cuò)誤是假設(shè)parseInt返回的整數(shù)是基于10進(jìn)制的。別忘記第二個(gè)參數(shù)基數(shù),它能是從2到36之間的任何值。為了確保你不會(huì)弄錯(cuò),請(qǐng)一直包含第二個(gè)參數(shù)。
parseInt('09/10/08'); //0parseInt(’09/10/08′,10); //9, 它最可能是你想從一個(gè)日期中得到的值
如果parseInt沒有提供第二個(gè)參數(shù),則前綴為 ‘0x’ 的字符串被當(dāng)作十六進(jìn)制,前綴為 ‘0′ 的字符串被當(dāng)作八進(jìn)制。所有其它字符串都被當(dāng)作是十進(jìn)制的。如果 numString 的前綴不能解釋為整數(shù),則返回 NaN(而不是數(shù)字)。
另一個(gè)常見的錯(cuò)誤是忘記使用“this”。在JavaScript對(duì)象中定義的函數(shù)訪問(wèn)這個(gè)對(duì)象的屬性,但沒有使用引用標(biāo)識(shí)符“this”。例如,下面是錯(cuò)誤的:
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文章用通俗易懂的英文表達(dá)了this綁定的問(wèn)題。
對(duì)this使用最大的陷阱是this在使用過(guò)程中其引用會(huì)發(fā)生改變:
<input type="button" value="Gotcha!" id="MyButton"><script>var MyObject = function () {this.alertMessage = "Javascript rules";this.ClickHandler = function() {alert(this.alertMessage );//返回結(jié)果不是”JavaScript rules”,執(zhí)行MyObject.ClickHandler時(shí),//this的引用實(shí)際上指向的是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);}}();
類似問(wèn)題的更多細(xì)節(jié)和解決方案請(qǐng)看《JavaScript作用域的問(wèn)題》。
當(dāng)給函數(shù)增加一個(gè)參數(shù)時(shí),一個(gè)常見的錯(cuò)誤是忘記更新這個(gè)函數(shù)的所有調(diào)用。如果你需要在已經(jīng)被調(diào)用的函數(shù)中增加一個(gè)參數(shù)來(lái)處理一個(gè)特殊情況下的調(diào)用,請(qǐng)給這個(gè)函數(shù)中的這個(gè)參數(shù)設(shè)置默認(rèn)值,以防萬(wàn)一在眾多腳本中的眾多調(diào)用中的一個(gè)忘記更新。
function addressFunction(address, city, state, country){country = country || “US”; //如果沒有傳入country,假設(shè) “US”span>//剩下代碼}
你也能通過(guò)獲取arguments來(lái)解決。但是在這篇文章我們的注意力在陷阱上。同時(shí)在《Javascript風(fēng)格要素(2)》也介紹了||巧妙應(yīng)用。
在JavaScript中關(guān)鍵字for有兩種使用方式,一個(gè)是for語(yǔ)句,一個(gè)是for/in語(yǔ)句。for/in語(yǔ)句將遍歷所有的對(duì)象屬性(attribute),包括方法和屬性(property)。決不能使用for/in來(lái)遍歷數(shù)組:僅在當(dāng)需要遍歷對(duì)象屬性和方法時(shí)才使用for/in。
為了解決這個(gè)問(wèn)題,大體上你可以對(duì)對(duì)象使用 for … in,對(duì)數(shù)組使用for循環(huán):
listItems = document.getElementsByTagName('li');for (var listitem in listItems){//這里將遍歷這個(gè)對(duì)象的所有屬性和方法,包括原生的方法和屬性,但不遍歷這個(gè)數(shù)組:出錯(cuò)了!}//因?yàn)槟阋h(huán)的是數(shù)組對(duì)象,所用for循環(huán)for ( var i = 0; i < listItems.length; i++) {//這是真正你想要的}對(duì)象的有些屬性以相同的方式標(biāo)記成只讀的、永久的或不可列舉的,這些屬性for/in無(wú)法枚舉。實(shí)際上,for/in循環(huán)
會(huì)遍歷所有對(duì)象的所有可能屬性,包括函數(shù)和原型中的屬性。所有修改原型屬性可能對(duì)for/in循環(huán)帶來(lái)致命的危害,所以需要采用hasOwnProperty和typeof做一些必要的過(guò)濾,最好是用for來(lái)代替for/in。
switch語(yǔ)句
Estelle Weyl寫了一篇switch statement quirks,其要點(diǎn)是:
- 沒有數(shù)據(jù)類型轉(zhuǎn)換
- 一個(gè)匹配,所有的表達(dá)式都將執(zhí)行直到后面的break或return語(yǔ)句執(zhí)行
- 你可以對(duì)一個(gè)單獨(dú)語(yǔ)句塊使用多個(gè)case從句
undefined ≠ null
null是一個(gè)對(duì)象,undefined是一個(gè)屬性、方法或變量。存在null是因?yàn)閷?duì)象被定義。如果對(duì)象沒有被定義,而測(cè)試它是否是null,但因?yàn)闆]有被定義,它無(wú)法測(cè)試到,而且會(huì)拋出錯(cuò)誤。
if(myObject !== null && typeof(myObject) !== 'undefined') {//如果myObject是undefined,它不能測(cè)試是否為null,而且還會(huì)拋出錯(cuò)誤}if(typeof(myObject) !== 'undefined' && myObject !== null) {//處理myObject的代碼}
Harish Mallipeddi對(duì)undefined和null有一個(gè)說(shuō)明。
事件處理陷阱
剛接觸事件處理時(shí)最常見的寫法就是類似:
window.onclick = MyOnClickMethod
這種做法不僅非常容易出現(xiàn)后面的window.onclick事件覆蓋掉前面的事件,還可能導(dǎo)致大名頂頂?shù)腎E內(nèi)存泄露問(wèn)題。為了解決類似問(wèn)題,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);
當(dāng)然在JavaScript庫(kù)盛行的現(xiàn)在,使用封裝好的事件處理機(jī)制是一個(gè)很好的選擇,比如在YUI中就可以這樣寫:
YAHOO.util.Event.addListener(window, "click", MyOnClickMethod);