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