本章主要幫助大家寫出高質量的JS程式碼的方法,模式和習慣,例如:避免使用全域性變數,使用單個的var變數宣告,快取for迴圈的長度變數length等
一、儘量避免使用全域性變數
1 每一個js環境都有一個全域性物件,通過this可以訪問,建立的每一個全域性變數都歸這個全域性物件所有,在瀏覽器中,這個全域性物件this等於window(在其他環境中,this物件不一定是window物件)
var sss="sss";
this.sss;//"sss"
window.sss;//"sss"
1.1全域性變數導致的問題
1,與第三方JS庫發生命名衝突;2,與廣告合作伙伴的指令碼發生命名衝突;3,與來自第三方的統計指令碼或者分析指令碼發生命名衝突;4,程式碼移植,如果你的程式碼換個環境執行,可能與另外一個環境的程式碼相沖突
2, 全域性變數總是出現的原因:1,JS有暗示全域性變數的概念,即任何變數,如果沒有宣告過,那麼就是全域性變數;2,隱式的建立了全域性變數反模式--使用var宣告的鏈式賦值
var a=b=0;
//這一切的原因是操作符的優先順序,=操作符的優先順序是從右向左,所以上面的例子,實際上相當於
//var a=(b=0);因此相當於隱式地建立了全域性變數b,
/*正確的寫法:
*var a,b,c;
*a=b=c=0;
*/
1.2 變數釋放(即delete刪除變數)時的副作用
1,隱含的全域性變數(即不宣告而直接使用的變數)與明確定義的全域性變數(即使用var定義的全域性變數)的不同之處在於,能否使用delete操作符刪除該變數
首先我們理解一下delete操作符,delete操作符是用來刪除物件的屬性的,使用var定義的全域性變數,不能被刪除,而隱含的全域性變數是可以刪除的,因此隱含的全域性變數其實並不是真正意義上的變數,而是作為全域性物件的屬性存在的
var m="111";
delete m;//false
console.log(m);//
fff="d";
delete fff;//true
console.log(fff);//Uncaught ReferenceError: fff is not defined
1.3 訪問全域性物件
按照以下方式獲取全域性物件,因為函式的呼叫(這裡不包括使用new操作符執行的函式)一般都指向全域性物件
但是在ECMAScript5的嚴格模式下不能如此使用,嚴格模式下對全域性物件的訪問:將庫程式碼打包到一個直接函式,然後傳遞一個引用給this,即把this看成傳遞到直接函式的一個引數。
var global=(function(){
return this;
})();
1.4 單一var模式:只使用一個var,在函式的頂部對該函式中所有的變數進行宣告
優點:
1、在一個位置可以查詢到函式所需要的所有區域性變數;
2、防止變數未宣告就使用;
3、更少編碼;
4、宣告的時候進行初始化,防止在後期使用時出現邏輯錯誤(數字變數當成字串這樣低階的錯誤)
5、將DOM引用賦值給區域性變數,不需要每次都去重新進行DOM搜尋,可大量節約時間
function fun(){
var a=1,
b=2,
my={},
i,
j;
//函式體
}
function fun(){
var element=document.getElementById("result"),
style=element.style;
}
1.5、提升:無論在js函式內的任意位置宣告的變數,效果都等同於在函式頂部進行宣告
JS允許在函式中的任意位置宣告變數,但無論在哪裡宣告最終都會被提升到函式的頂部,因此先使用後宣告可能會導致邏輯問題
js分為預編譯階段與執行階段,
預編譯階段:
1 對使用function語句宣告的函式進行處理,不僅按照函式名按照變數識別符號進行索引,對函式體也進行處理(即對函式名(也可以認為是變數名)進行賦值),如果出現同名函式,則會用後者覆蓋前者(即後者的賦值覆蓋前者)
2 對匿名函式在此階段視而不見
3 對使用var宣告的變數進行索引,但是對變數的初始化忽略掉,
在預編譯階段遇到var宣告的變數,如果前面沒有做過初始化,就是undefined,如果該變數在前面是函式名,那麼其值就是函式體,這裡不做覆蓋
function fun(){}
/*變數名fun在預編譯階段,不僅對fun這個變數名進行索引,對其函式體也進行了處理,即fun的值為函式體*/
console.log(fun);//function()
var fun="";
/*使用var宣告的變數名fun在預編譯階段,只對其變數名進行索引,不對其進行賦值,所以這fun的初始化值不會覆蓋上面的函式體,但是在執行階段會覆蓋*/
console.log(fun);//
執行階段:
1 對匿名函式按表示式逐行進行解釋執行
2 為預編譯階段索引的變數讀取初始值,由於執行階段是逐行解釋執行的,所以如果在賦值語句前面進行呼叫的話,值應該為預編譯階段的
function fun(){
alert(myName);//undefined
var myName="local";
alert(myName);//local
}
fun();
二、for迴圈
for迴圈一般用於遍歷陣列或者類陣列物件(arguments,html容器等)
優化點:1 對陣列的長度進行快取,var len=arr.length
2 使用單一的var模式,把所有的變數都提到函式體的開始,使用一個var進行宣告
3 i++替換掉i=i+1與i+=1
4 在沒有要求的時候,遞減到0,即i--
1 一般情況下的for迴圈
for(var i=0;i<arr.length;i++){
//對陣列或者類陣列物件中的元素的操作
}
缺點:顯然每次迴圈都會計算一下要訪問資料的長度,這樣效率就會降低,尤其是當訪問的是HTML容器物件時
改進:將要訪問資料的長度快取起來,對訪問速度的提升相當顯著,ie可提高170倍
for(var i=0,max=ayy.length;i<max;i++){
}
2 進一步改進:結合我們前面提到單var變數模式
function fun(){
var i=0,ayy=[],max;//將所有該函式中用到的變數都宣告在函式的頂部
for(i=0,max=ayy.length;i<max;i++){
}
}
該模式的缺點:複製貼上的時候,要保證將所需變數的宣告全部複製進去
3 JSLint推薦使用++與--,即逐步遞增或者遞減(這個有不同的意見,可以保留)
4 更進一步改進:從最後一個元素,逐個遍歷到第一個元素,將i與0比較,比i與非0的max比較效率要高
var a,b,c,d,e,f;
a=+new Date();
for(i=10000000;i>=0;i--){
}
b=+new Date();
console.log(b-a);
// c=+new Date();
for(j=0;j<=10000000;j++){
}
d=+new Date();
console.log(d-c);
//
e=+new Date();
for(k=10000000;k--;){
}
f=+new Date();
console.log(f-e);//
三、for-in迴圈
for-in主要用於遍歷非陣列物件,稱之為列舉
1 當要遍歷物件屬性,並過濾掉原型中的屬性和方法時,使用hasOwnProperty()方法
var man={
a:1,
b:2,
c:3
},i; for(i in man){
if(man.hasOwnProperty(i)){//當確定不了物件屬性和原型中的內容時,使用hasOwnProperty方法加以判斷,如果可以確定則可以省略該判斷,提高效率
console.log(i+":"+man[i]);
}
}
2 在上面我們提到對原型鏈中的屬性和方法進行過濾時,使用hasOwnProperty方法,由於hasOwnProperty方法是屬於Object原型的方法,所以我們可以這樣使用,這樣避免命名衝突(即man物件也有一個hasOwnProperty方法,那麼就與我們想要過濾使用的hasOwnProperty方法衝突了)
var i,hasOwn=Object.prototype.hasOwnProperty,man={
"name":"jim",
"age":12
};
for(i in man){
if(hasOwn.call(man,i)){
console.log(i+":"+man[i]);
}
} var i,hasOwn=Object.prototype.hasOwnProperty,man={
"name":"jim",
"age":12,
hasOwnProperty:function(){console.log("man's hasownproperty")}
};
for(i in man){
if(man.hasOwnProperty(i)){
console.log(i+":"+man[i]);
}
}
四、不要給內建物件的原型增加方法(這裡的內建物件指的是Date,Math,Array,String,Event,Object等)
我們經常給建構函式的原型增一些方法,但是給js的內建物件的建構函式的原型增加方法會嚴重影響可維護性,因此這裡不推薦
下面的幾種情況例外
1,可以新增ECMAScript5中描述的確尚未實現的方法,等待ECMAScript加以實現
2,某些瀏覽器的JS引擎已經實現了該方法
3,寫成文件形式,與團隊充分溝通
給內建物件新增自定義方法
if(typeof Object.prototype.MyMethod!=="function"){
Object.prototype.MyMethod=function(){
};
}
五、switch模式
1 每個case語句結尾都有一個break
2 使用default來作為switch的結束
六、避免使用隱式型別轉換
1 什麼是隱式型別轉換:false==0、""==false
2 為了避免出現上面的情況,我們在使用比較語句的時候儘量使用===和!==
6.1 避免使用eval
1 eval可以將任何的字串當作js程式碼執行
2 將ajax返回的資料字串轉換成物件時,不推薦使用eval,推薦JSON.parse或者JSON.org網站的類庫,因為eval執行的字串可能是一家被篡改過的程式碼
3 setInterval/setTimeout傳遞引數時,也會導致類似於eval的隱患,因此儘量避免使用
4 new Function()與eval和相似,使用時要小心
如果一定要使用eval,可以使用new Function來替代,因為new Function將在區域性函式空間執行,因此程式碼中var定義的變數不會成為全域性變數
另外一個避免eval中使用var定義的變數成為全域性變數,將eval放到一個即時函式中
new Function(或者Function),這個方法是隻看到全域性變數,對區域性變數影響較小
setTimeout("fun(1,2,3)",100);//反模式 //推薦模式
setTimeout(function(){
fun(1,2,3);
},100);
var jsString1='var aaaaaa=1;console.log(aaaaaa);';
var jsString2='var bbbbbbb=2;console.log(bbbbbbb);';
var jsString3='var ccccccc=3;console.log(ccccccc);';
eval(jsString1);//
new Function(jsString2)();//2new Function將在區域性函式空間執行
/*另外一個避免eval中使用var定義的變數成為全域性變數,將eval放到一個即時函式中*/
(function(){
eval(jsString3);
})();//
console.log(aaaaaa);//1eval程式碼裡面var定義的變數會成為全域性變數
console.log(bbbbbbb);//ReferenceError: ccccccc is not defined
console.log(ccccccc);//ReferenceError: ccccccc is not defined
new Function(或者Function),這個方法是隻看到全域性變數,對區域性變數影響較小 function fff(){
var bbb='bbb';
var jsString='console.log(bbb);'
new Function(jsString)();//或者Function(jsString)()
}
fff();//ReferenceError: bbb is not defined
var a="global a";
function fff(){
var a='local a';
var jsString='console.log(a);'
new Function(jsString)();//或者Function(jsString)()
}
fff();//global a
七、使用parseInt方法時,第二個引數儘量不要省略
八、編碼約定
8.1 縮排:使用tab鍵進行縮排
8.2 大括號:for與if語句最好都使用大括號
8.3 開放大括號的位置:和語句放在同一行
/*根據分號插入機制,也就是一行結束如果後面沒有分號,會自動插入分號*/
return
{
"name":"Amy",
"age":18
}; /*相當於
return ;
{
"name":"Amy",
"age":18
};
*因此下面這種方式更合適
*/
return {
"name":"Amy",
"age":18
};
九、命名約定
9.1 建構函式的首字母大寫
9.2 分隔單詞:函式/方法名:小駝峰式;變數:小寫字母,下劃線分隔;常量:全部大寫;私有變數/方法:下劃線做字首
十、編寫註釋
十一、編寫API文件
十二、編寫可讀性強的程式碼
十三、同行互查
十四、在正式釋出時精簡程式碼
十五、執行JSLint