本章主要幫助大家寫出高質量的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