關於javascript代碼最佳化的8點建議

來源:互聯網
上載者:User

標籤:reveal   通用   alert   直接   ==   txt   select   radius   target   

前面的話

  本文將詳細介紹JS編程風格的幾個要點

 

松耦合

  當修改一個組件而不需要更改其他組件時,就做到了松耦合

  1、將JS從CSS中抽離:不要使用CSS運算式

//不好的做法.box{width: expression(document.body.offsetWidth + ’px‘)}

  2、將CSS從JS中抽離:通過JS修改CSS樣式時,使用className或classList,不要逐條修改style樣式

//不好的做法一ele.style.color = ‘red‘;ele.style.left= ‘10px‘;//不好的做法二ele.style.cssText =‘color:red;left:10px;‘;
.reveal{color:red;left:10px;}//好的做法一ele.className += ‘reveal‘;//好的做法二ele.classList.add(‘reveal‘);

  3、將JS從HTML中抽離:從JS檔案放入外置檔案中

  4、將HTML從JS中抽離:不要在innerHTML中拼接DOM結構,而是使用字串模板,如handlerbars

 

全域變數

  建立全域變數被認為是糟糕的實踐,尤其在團隊開發的大背景下更是問題多多。隨著代碼量的增長,全域變數會導致一些非常重要的可維護性難題,全域變數越多,引入錯誤的機率會變得越高

  一般而言,有如下三種解決辦法

  1、零全域變數

  實現方法是使用一個立即調用函數IIFE並將所有指令碼放置其中

(function(){  var doc = win.document;})(window);

  這種模式的使用情境有限,只要代碼需要被其他的代碼所依賴,或者需要在運行中被不斷擴充或修改,就不能使用這種方式

  2、單全域變數和命名空間

  依賴儘可能少的全域變數,即只建立一個全域變數,使用單變數模式,如YUI或jQuery

  單全域變數,即所建立的這個唯一全域對象名是獨一無二的,並將所有的功能代碼都掛載到這個全域對象上。因此,每個可能的全域變數,都成為唯一全域變數的屬性,從而不會建立多個全域變數

  命名空間是簡單的通過全域對象的單一屬性工作表示的功能性分組。比如Y.DOM下的所有方法都是和DOM操作相關的,Y.Event下的所有方法都是和事件相關的。常見的約定是每個檔案中都通過新的全域對象來聲明自己的命名空間

  3、使用模組

  模組是一種通用的功能片段,它並沒有建立新的全域變數或命名空間。相反,所有的這些代碼都存放於一個表示執行一個任務或發布一個介面的單函數中。可以用一個名稱來表示這個模組,同樣這個模組可以依賴其他模組

 

事件處理

  將事件處理相關的代碼和事件環境耦合在一起,導致可維護性很糟糕

  1、隔離應用邏輯

  將應用邏輯從所有事件處理常式中抽離出來是一種最佳實務,將應用邏輯和事件處理的代碼拆分開來

//不好的做法function handleClick(event){  var popup = document.getElementById(‘popup‘);  popup.style.left = event.clientX + ‘px‘;  popup.style.top = event.clientY + ‘px‘;  popup.className = ‘reveal‘;}addListener(element,‘click‘,handleClick);//好的做法var MyApplication = {  handleClick: function(event){    this.showPopup(event);  },  showPopup: function(event){    var popup = document.getElementById(‘popup‘);    popup.style.left = event.clientX + ‘px‘;    popup.style.top = event.clientY + ‘px‘;    popup.className = ‘reveal‘;  }};addListener(element,‘click‘,function(event){  MyApplication.handleClick(event);});

  2、不要分發事件對象

  應用邏輯不應當依賴於event對象來正確完成功能,方法介面應該表明哪些資料是必要的。代碼不清晰就會導致bug。最好的辦法是讓事件處理常式使用event對象來處理事件,然後拿到所有需要的資料傳給應用邏輯

//改進的做法var MyApplication = {  handleClick: function(event){    this.showPopup(event.clientX,event.clientY);  },  showPopup: function(x,y){    var popup = document.getElementById(‘popup‘);    popup.style.left = x + ‘px‘;    popup.style.top = y + ‘px‘;    popup.className = ‘reveal‘;  }};addListener(element,‘click‘,function(event){  MyApplication.handleClick(event);});

  當處理事件時,最好讓事件程式成為接觸到event對象的唯一的函數。事件處理常式應當在進入應用邏輯之前針對event對象執行任何必要的操作,包括阻止事件冒泡,都應當直接包含在事件處理常式中

//改進的做法var MyApplication = {  handleClick: function(event){    event.preventDefault();    event.stopPropagation();    this.showPopup(event.clientX,event.clientY);  },  showPopup: function(x,y){    var popup = document.getElementById(‘popup‘);    popup.style.left = x + ‘px‘;    popup.style.top = y + ‘px‘;    popup.className = ‘reveal‘;  }};addListener(element,‘click‘,function(event){  MyApplication.handleClick(event);});

 

配置資料

  代碼無非是定義一些指令的集合讓電腦來執行。我們常常將資料傳入電腦,由指令對資料進行操作,並最終產生一個結果。當不得不修改資料時,可能會帶來一些不必要的風險。應當將關鍵資料從代碼中抽離出來 

  配置資料是指導在應用中寫死的值,且將來可能會被修改,包括如下內容

1、URL2、需要展現給使用者的字串3、重複的值4、配置項5、任何可能發生變更的值

  下面是未處理配置資料的做法

//不好的做法function validate(value){  if(!value){    alert(‘Invalid value‘);    location.href="/errors/invalid.php";  }}function toggleSelected(element){  if(hasClass(element,‘selected‘)){    removeClass(element,‘selected‘);  }else{    addClass(element,‘selected‘);  }}

  下面代碼中將配置資料儲存在了config對象中,config對象的每個屬性都儲存了一個資料片段,每個屬性名稱都有首碼,用以表明資料的類型(MSG表示展現給使用者的資訊,URL表示網路地址,CSS表示這是一個className)。當然,也可以將整個config對象放到單獨的檔案中,這樣對配置資料的修改可以完全和使用這個資料的代碼隔離開來

//好的做法var config = {  MSG_INVALID_VALUE: ‘Invalid value‘,  URL_INVALID:‘/errors/invalid.php‘,  CSS_SELECTED:‘selected‘}function validate(value){  if(!value){    alert(config.MSG_INVALID_VALUE);    location.href=config.URL_INVALID;  }}function toggleSelected(element){  if(hasClass(element,config.CSS_SELECTED)){    removeClass(element,config.CSS_SELECTED);  }else{    addClass(element,config.CSS_SELECTED);  }}

 

選取器最佳化

  將選取器選擇到的元素作為對象的靜態屬性集中到一個地方統一管理

initializeElements: function() {    var eles = app.Eles;    for (var name in eles) {        if (eles.hasOwnProperty(name)) {            this[name] = $(eles[name]);        }    }}

  下面是一個例子

//好的做法
app.Eles = { widgetDiv: ".left-widget div", inputResize: ‘.input-resize‘, hr: ‘.hr‘, txt: ‘.input-group-btn button‘, cus: ‘#paper-type-cus‘, hid: ‘#hidden‘, mainCon: ‘#mainCon‘, rulerX: ‘.ruler-x‘, rulerY: ‘.ruler-y‘,};

 

函數最佳化

【提煉函數】

  在javascript開發中,大部分時間都在與函數打交道,所以希望這些函數有著良好的命名,函數體內包含的邏輯清晰明了。如果一個函數過長,不得不加上若干注釋才能讓這個函數顯得易讀一些,那這些函數就很有必要進行重構

  如果在函數中有一段代碼可以被獨立出來,那最好把這些代碼放進另外一個獨立的函數中。這是一種很常見的最佳化工作,這樣做的好處主要有以下幾點

  1、避免出現超大函數

  2、獨立出來的函數有助於代碼複用

  3、獨立出來的函數更容易被覆寫

  4、獨立出來的函數如果擁有一個良好的命名,它本身就起到了注釋的作用

  比如在一個負責取得使用者資訊的函數裡面,還需要列印跟使用者資訊有關的log,那麼列印log的語句就可以被封裝在一個獨立的函數裡:

var getUserInfo = function(){    ajax( ‘http:// xxx.com/userInfo‘, function( data ){        console.log( ‘userId: ‘ + data.userId );        console.log( ‘userName: ‘ + data.userName );        console.log( ‘nickName: ‘ + data.nickName );    });};//改成:var getUserInfo = function(){    ajax( ‘http:// xxx.com/userInfo‘, function( data ){        printDetails( data );    });};var printDetails = function( data ){    console.log( ‘userId: ‘ + data.userId );    console.log( ‘userName: ‘ + data.userName );    console.log( ‘nickName: ‘ + data.nickName );};

【盡量減少參數數量】

  如果調用一個函數時需要傳入多個參數,那這個函數是讓人望而生畏的,必須搞清楚這些參數代表的含義,必須小心翼翼地把它們按照順序傳入該函數。在實際開發中,向函數傳遞參數不可避免,但應該盡量減少函數接收的參數數量。下面舉個非常簡單的樣本。有一個畫圖函數draw,它現在只能繪製正方形,接收了3個參數,分別是圖形的width、heigth以及square:

var draw = function(width,height,square){};

  但實際上正方形的面積是可以通過width和height計算出來的,於是我們可以把參數square從draw函數中去掉:

var draw = function( width, height ){    var square = width * height;};

  假設以後這個draw函數開始支援繪製圓形,需要把參數width和height換成半徑radius,但圖形的面積square始終不應該由客戶傳入,而是應該在draw函數內部,由傳入的參數加上一定的規則計算得來。此時,可以使用原則模式,讓draw函數成為一個支援繪製多種圖形的函數

【傳遞對象參數代替過長的參數列表】 

  有時候一個函數有可能接收多個參數,而參數的數量越多,函數就越難理解和使用。使用該函數的人首先得搞明白全部參數的含義,在使用的時候,還要小心翼翼,以免少傳了某個參數或者把兩個參數搞反了位置。如果想在第3個參數和第4個參數之中增加一個新的參數,就會涉及許多代碼的修改,代碼如下:

var setUserInfo = function( id, name, address, sex, mobile, qq ){    console.log( ‘id= ‘ + id );    console.log( ‘name= ‘ +name );    console.log( ‘address= ‘ + address );    console.log( ‘sex= ‘ + sex );    console.log( ‘mobile= ‘ + mobile );    console.log( ‘qq= ‘ + qq );};setUserInfo( 1314, ‘xiaohuochai‘, ‘beijing‘, ‘male‘, ‘150********‘, 121631835 );

  這時可以把參數都放入一個對象內,然後把該對象傳入setUserInfo函數,setUserInfo函數需要的資料可以自行從該對象裡擷取。現在不用再關心參數的數量和順序,只要保證參數對應的key值不變就可以了:

    var setUserInfo = function( obj ){        console.log( ‘id= ‘ + obj.id );        console.log( ‘name= ‘ + obj.name );        console.log( ‘address= ‘ + obj.address );        console.log( ‘sex= ‘ + obj.sex );        console.log( ‘mobile= ‘ + obj.mobile );        console.log( ‘qq= ‘ + obj.qq );    };    setUserInfo({        id: 1314,        name: ‘xiaohuochai‘,        address: ‘beijing‘,        sex: ‘male‘,        mobile: ‘150********‘,        qq: 121631835    });

 

條件最佳化

【合并條件片段】

  如果一個函數體內有一些條件分支語句,而這些條件分支語句內部散布了一些重複的代碼,那麼就有必要進行合并去重工作。假如有一個分頁函數paging,該函數接收一個參數currPage,currPage表示即將跳轉的頁碼。在跳轉之前,為防止currPage傳入過小或者過大的數字,要手動對它的值進行修正,詳見如下虛擬碼:

var paging = function( currPage ){    if ( currPage <= 0 ){        currPage = 0;        jump( currPage ); // 跳轉    }else if ( currPage >= totalPage ){        currPage = totalPage;        jump( currPage ); // 跳轉    }else{        jump( currPage ); // 跳轉    }};

  可以看到,負責跳轉的代碼jump(currPage)在每個條件分支內都出現了,所以完全可以把這句代碼獨立出來:

var paging = function( currPage ){    if ( currPage <= 0 ){        currPage = 0;    }else if ( currPage >= totalPage ){        currPage = totalPage;    }    jump( currPage ); // 把jump 函數獨立出來};

【把條件分支語句提煉成函數】

  在程式設計中,複雜的條件分支語句是導致程式難以閱讀和理解的重要原因,而且容易導致一個龐大的函數。假設現在有一個需求是編寫一個計算商品價格的getPrice函數,商品的計算只有一個規則:如果當前正處於夏季,那麼全部商品將以8折出售。代碼如下:

var getPrice = function( price ){    var date = new Date();    if ( date.getMonth() >= 6 && date.getMonth() <= 9 ){ // 夏天        return price * 0.8;    }    return price;};

  觀察這句代碼:

date.getMonth()>=6&&date.getMonth()<=9

  這句代碼要表達的意思很簡單,就是判斷當前是否正處於夏天(7~10月)。儘管這句代碼很短小,但代碼錶達的意圖和代碼自身還存在一些距離,閱讀代碼的人必須要多花一些精力才能明白它傳達的意圖。其實可以把這句代碼提煉成一個單獨的函數,既能更準確地表達代碼的意思,函數名本身又能起到注釋的作用。代碼如下:

var isSummer = function(){    var date = new Date();    return date.getMonth() >= 6 && date.getMonth() <= 9;};var getPrice = function( price ){    if ( isSummer() ){ // 夏天        return price * 0.8;    }    return price;};

【提前讓函數退出代替嵌套條件分支】

  許多程式員都有這樣一種觀念:“每個函數只能有一個入口和一個出口。”現代程式設計語言都會限制函數只有一個入口。但關於“函數只有一個出口”,往往會有一些不同的看法。下面這段虛擬碼是遵守“函數只有一個出口的”的典型代碼:

var del = function( obj ){    var ret;    if ( !obj.isReadOnly ){ // 不為唯讀才能被刪除        if ( obj.isFolder ){ // 如果是檔案夾            ret = deleteFolder( obj );        }else if ( obj.isFile ){ // 如果是檔案            ret = deleteFile( obj );        }    }    return ret;};

  嵌套的條件分支語句絕對是代碼維護者的噩夢,對於閱讀代碼的人來說,嵌套的if、else語句相比平鋪的if、else,在閱讀和理解上更加困難。嵌套的條件分支往往是由一些深信“每個函數只能有一個出口的”程式員寫出的。但實際上,如果對函數的剩餘部分不感興趣,那就應該立即退出。引導閱讀者去看一些沒有用的else片段,只會妨礙他們對程式的理解

  於是可以挑選一些條件分支,在進入這些條件分支之後,就立即讓這個函數退出。要做到這一點,有一個常見的技巧,即在面對一個嵌套的if分支時,可以把外層if運算式進行反轉。重構後的del函數如下:

var del = function( obj ){    if ( obj.isReadOnly ){ // 反轉if 運算式        return;    }    if ( obj.isFolder ){        return deleteFolder( obj );    }    if ( obj.isFile ){        return deleteFile( obj );    }};

 

迴圈最佳化

【合理使用迴圈】

  在函數體內,如果有些代碼實際上負責的是一些重複性的工作,那麼合理利用迴圈不僅可以完成同樣的功能,還可以使代碼量更少。下面有一段建立XHR對象的代碼,為了簡化樣本,只考慮版本9以下的IE瀏覽器,代碼如下:

var createXHR = function(){    var xhr;    try{        xhr = new ActiveXObject( ‘MSXML2.XMLHttp.6.0‘ );    }catch(e){        try{            xhr = new ActiveXObject( ‘MSXML2.XMLHttp.3.0‘ );        }catch(e){            xhr = new ActiveXObject( ‘MSXML2.XMLHttp‘ );        }    }    return xhr;};var xhr = createXHR();

  下面靈活地運用迴圈,可以得到跟上面代碼一樣的效果:

//下面我們靈活地運用迴圈,可以得到跟上面代碼一樣的效果:var createXHR = function(){    var versions= [ ‘MSXML2.XMLHttp.6.0ddd‘, ‘MSXML2.XMLHttp.3.0‘, ‘MSXML2.XMLHttp‘ ];    for ( var i = 0, version; version = versions[ i++ ]; ){        try{            return new ActiveXObject( version );        }catch(e){        }    }};var xhr = createXHR();

【用return退出多重迴圈】

  假設在函數體內有一個兩重迴圈語句,需要在內層迴圈中判斷,當達到某個臨界條件時退出外層的迴圈。大多數時候會引入一個控制標記變數:

var func = function(){    var flag = false;    for ( var i = 0; i < 10; i++ ){        for ( var j = 0; j < 10; j++ ){            if ( i * j >30 ){                flag = true;                break;            }        }        if ( flag === true ){            break;        }    }};

  第二種做法是設定迴圈標記:

var func = function(){    outerloop:    for ( var i = 0; i < 10; i++ ){        innerloop:        for ( var j = 0; j < 10; j++ ){            if ( i * j >30 ){                break outerloop;            }        }    }};

  這兩種做法無疑都讓人頭暈目眩,更簡單的做法是在需要中止迴圈的時候直接退出整個方法:

var func = function(){    for ( var i = 0; i < 10; i++ ){        for ( var j = 0; j < 10; j++ ){            if ( i * j >30 ){                return;            }        }    }};

  當然用return直接退出方法會帶來一個問題,如果在迴圈之後還有一些將被執行的代碼呢?如果提前退出了整個方法,這些代碼就得不到被執行的機會:

var func = function(){    for ( var i = 0; i < 10; i++ ){        for ( var j = 0; j < 10; j++ ){            if ( i * j >30 ){                return;            }        }    }    console.log( i ); // 這句代碼沒有機會被執行};

  為瞭解決這個問題,可以把迴圈後面的代碼放到return後面,如果代碼比較多,就應該把它們提煉成一個單獨的函數:

var print = function( i ){    console.log( i );};var func = function(){    for ( var i = 0; i < 10; i++ ){        for ( var j = 0; j < 10; j++ ){            if ( i * j >30 ){                return print( i );            }        }    }};
func();

 

關於javascript代碼最佳化的8點建議

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.