js編寫的可維護與性能優化

可維護

解耦HTML/JavaScript

一、採用引入js文件的方式取代在html頁面寫js代碼
二、避免在js中建立大量html
(1)當js用於插入數據時,儘可能不要直接插入標記。通常能夠在頁面中直接包含並隱藏標記,而後等到整個頁面渲染好以後,就能夠用js顯示該標記,而非生成它
(2)也能夠經過Ajax請求得到更多要顯示的html,這個方法可讓一樣的渲染層(PHP、JSP、Ruby等等)來輸出標記,而不是直接嵌在jsphp

解耦CSS/JavaScript

儘可能不要在js中更改樣式,而是採用動態更改樣式類,從而作到對於樣式的問題應只查看CSS文件來解決,例:html

element.style.color = 'red';
element.style.backgroundColor = 'blue';

更改後:
element.className = 'edit';

解耦應用邏輯/事件處理程序

每一個web應用通常都有至關多的事件處理程序,監聽這無數不一樣的事件,然而,不多有能仔細得將應用邏輯從事件處理程序中分離的,以下:web

function handleKeyPress (event) {
    if(event.keyCode == 1){
        let target = event;
        let value = 5 * parseInt(target.value);
        if(value > 10){
            document.getElementById('tip-msg').style.display = 'block';
        }
    }
}

這個事件處理程序處理包含了應用邏輯,還進行了事件處理,這種方式的問題有其雙重性
(1)、處理經過事件以外就沒有辦法執行應用邏輯
(2)、調試困難,若是沒有發生預想的結果,並不知道是事件沒被調用仍是應用邏輯失敗
(3)、若是後續的事件須要執行相同的應用邏輯,那麼就必須複製功能代碼或將代碼抽到一個單獨的函數中因此就好進行二者的解耦,即一個事件處理程序應該從事件對象中提取相關信息,並將這些信息傳送處處理應用邏輯的某個方法中,前面例子重寫以下:數組

function validateValue (value) {
    value = 5 * parseInt(value);
    if(value > 10){
        document.getElementById('tip-msg').style.display = 'block';
    }
}

function handleKeyPress (event) {
    if(event.keyCode == 13){
        let target = event;
        validateValue(target.value);
    }
}

從而使validateValue()中沒有任何東西會依賴於任何事件處理程序邏輯,他只接收一個值,並根據該值進行其餘處理
好處:若是事件最初只有鼠標事件觸發,那麼如今只需少許修改就能夠實現按鍵觸發性能優化

避免全局變量

最多建立一個全局變量,讓其餘對象和函數存在其中,如:app

//兩個全局變量——避免!!
let name= 'bad';
function sayName() {
    alert(name);
}    

//一個全局變量——推薦
let good = {
    name: 'nice',
    sayName: function () {
        alert(this.name);
    }
}

多人協做開發可使用命名空間函數

//建立全局對象
let wrox = {};

//爲Tony建立命名空間
wrox.Tony = {};

//爲Tony(能夠以人名劃分)建立方法
wrox.Tony.sayName = function () {
    ...
};

上述例子,wrox是全局變量,其餘命名空間在此之上建立,只要全部人都按這樣寫,那麼就不用小心不一樣開發者建立相同的方法等,由於它存在於不一樣的命名空間性能

避免與null進行比較

應該讓條件與預想的類型進行比較而不是與null測試

//bad
function sortArray(values) {
    if(values != null){
        ...
    }
}
//good
function sortArray(values) {
    if(values instanceof Array){
        ...
    }    
}

抽離常量

const constants = {
    INVALID_VALUE_MSG:'Invalid value!',
    INVALID_VALUE_URL:'/errors/invalid.php'
}

性能優化

由於訪問全局變量要比訪問局部變量慢,由於要遍歷做用域鏈,因此減小花費在做用域鏈上的時間,就能夠增長腳本的總體新能大數據

避免全局查找

//bad
function updateUI () {
    let imgs = document.getElementsByTagName('img');
    for(let i=0, len=imgs.length; i<len; i++){
        imgs[i].title = document.title + 'imge' + i;
    }
    let msg = document.getElementById('msg');
    msg.innerHTML = 'Upadate complete.';
}
//good
function updateUI () {
    let doc = document;
    let imgs = doc.getElementByTagName('img');
    for(let i=0, len=imgs.length; i<len; i++){
        imgs[i].title = doc.title + 'imge' + i;
    }
    let msg = doc.getElementById('msg');
    msg.innerHTML = 'Upadate complete.';
}

上面將document對象保存在doc變量中,而後替換原來的document,與原來相比,只需進行一次全局查找,速度確定更快

循環優化

循環優化基本步驟以下:
一、減值迭代——大多數循環使用一個從0開始、增長到某個特定值的迭代器。在不少狀況下,從最大值開始,在循環中的迭代器更加高效。
二、簡化終止條件——因爲每次循環過程都會計算終止條件,因此必須保證它儘量快。也就是說避免屬性查找或者其餘O(n)的操做。
三、簡化循環體——循環體是執行最多的,因此要確保其被最大限地優化,確保沒有某些能夠被很容易移除循環的密集計算。
四、使用後側試循環——最經常使用的for循環和while循環都是前測試循環。而入do—while這種後側試循環,能夠避免最初終止條件的計算

//基本for循環
for(let i=0;i<values.lenght;i++){
    process(values[i]);
}
//將終止條件從values.length的O(n)調用簡化成了0的O(1)調用
for(let i=values.length -1; i>=0; i--){
    process(values[i]);
}
//再進行改形成後測試循環,此處主要的優化是將終止條件和自減操做符組合成了單個語句
let i = values.length - 1;
if(i> -1){
    do{
        process(valeus[i]);
    }while(--i>=0)
}

最小語句數

一、多個聲明變量

//bad
let count = 5;
let color = 'blue';
let values = [1,2,3];
let now = new Date();
//good
let count = 5,
    color = 'blue',
    values = [1,2,3],
    now = new Date();

二、插入迭代值

//bad
let name = values[i];
i++;
//good
let name = values[i++];

三、使用數組和對象字面量

//bad
let values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
//bad
let person = new Object();
person.name = 'Tony';
person.age = 18;
person.sayName = function () {
    alert(this.name);
}

//good
let values = [123,456,789];
let person = {
    name: 'Tony',
    age: 18,
    sayName: function () {
        alert(this.name);
    }
}

優化DOM交互

一、最小現場更新

//bad
let list = document.getElementById('myList'),
    item,
    i;
for(i=0; i<10; i++){
    item = document.createElement('li');
    list = appendChilde(item);
    item.appendChild(document.createTextNode('Item' + i);
}

上述代碼爲列表添加了10個項目,每添加個項目時,都有2個現場更新:一個添加<li>元素,另外一個給他添加文本節點,這樣添加10個項目,這個操做總共要完成20個現場更新。
解決方法:
一、將列表從頁面上移除,最後進行更新,最後在將列表插回到一樣的位置,看似很美好,但這樣作會致使在每次更新的時候它會沒必要要的閃爍
二、使用文檔碎片來構建DOM結構,接着將其添加到list元素中,這個方式避免了閃爍,以下:

//good,只有一次現場更新
let list = document.getElementById('myList');
    framgent = document.createDocumentFragment(),
    itme,
    i;
for(i=0; i<10; i++){
    item = document.createElement('li');
    fragment.appendChild(item);
    item.appendChild(document.createTextNode('Item' + i));
}
list.appendChild(fragment);

二、使用innerHTML
有兩種方式在頁面上建立DOM節點:使用諸如createElement()appendChild()之類的DOM方法,以及使用innerHTML。對於小的DOM而言,二者效率差很少,對於大的DOM改動,使用innerHTML要快得多。
緣由:當把innerHTML設置爲某個值時,後臺會建立一個HTML解析器,而後使用內部的DOM調用來建立DOM結構,而非基於jsDOM調用,因爲內部方法是編譯好的而非解釋執行的,因此執行快得多,因此上面例子還能夠優化

let list = document.getElementById('myList'),
    html = '',
    i;
for(i=0; i<10; i++){
    html += `<li>Item${i}</li>`;
}    
list.innerHTML = html;
//tip一樣要避免屢次調用innerHTML,即要作到構建好一個字符串而後一次性調用innerHTML

使用事件代理

頁面上的事件處理程序的數量和頁面的響應用戶交互的速度之間是負相關的
任何冒泡的事件都不只僅能夠在事件的目標上進行處理,目標的任何祖先節點上也能處理,若是可能,在文檔級別附加事件處理程序,這樣就能夠處理整個頁面的事件

注意HTMLCollection

任什麼時候候要訪問HTMLCollection,不論是一個屬性仍是一個方法,都是在文檔上進行的一個查詢,這個查詢開銷很昂貴,爾等消費不起

let images = document.getElementsByTagName('img');
    imags,
    i,
    len;
for(i=0; len=images.length; i<len; i++){
    image = images[i];      //這裏保存了當前的image,在這以後就無須再訪問image的HTMLCollection了
}

tip:發生如下狀況會返回HTMLCollection對象:
(1)、進行了對getElementsByTagName()的調用;
(2)、獲取了元素的childNodes屬性;
(3)、獲取了元素的attribute屬性;
(4)、訪問了特殊的集合,如document.forms、document.images

展開循環(Duff)

當循環的次數是肯定的,消除循環並使用屢次函數調用每每更快,

//low
for(let i=0;i<3;i++){
    process(values[i]);
}
//fast,消除創建循環和處理終止條件的額外開銷
process(values[0]);
process(values[1]);
process(values[2]);

若是迭代次數事項不能肯定,可使用Duff裝置的技術
Duff:經過計算迭代的次數是否爲8的倍數將一個循環展開爲一系列語句

let iterations = Math.ceil(values.length / 8); //向上取整確保結果是整數
let startAt = values.length % 8; //獲取沒法經過上面進行處理的項,若是values.length爲10,那麼startAt爲2
let i = 0;

do {
    switch(startAt) {
        case 0: process(values[i++]);
        case 7: process(values[i++]);
        case 6: process(values[i++]);
        case 5: process(values[i++]);
        case 4: process(values[i++]);
        case 3: process(values[i++]);
        case 2: process(values[i++]);
        case 1: process(values[i++]);
    }
    startAt = 0;
} while (--iterations > 0);

上面運行的結果是先運行startAt次的process(),而後startAt設置爲0,後面循環都執行8次process(),展開循環能夠提高大數據集的處理速度。
do-while循環分紅2個單獨的循環可讓Duff更快

let iterations = Math.ceil(values.length / 8); //向上取整確保結果是整數
let startAt = values.length % 8; //獲取沒法經過上面進行處理的項,若是values.length爲10,那麼startAt爲2
let i = 0;

if(leftover > 0){           
    do{
        process(values[i++]);
    } while (--leftover > 0);
}
do {
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
    process(values[i++]);
} while (--iterations > 0);

上面代碼讓剩餘的計算部分不會在實際循環中處理,而是在一個初始化循環中進行除以8的操做,當處理掉了額外的元素,繼續執行每次調用8次process()的主循環,這個方法幾乎比原始的Duff裝置快40%
tip:以上只適用於處理大數據集

其餘性能優化注意事項

下面並不是主要影響性能的主要問題,應用得當,會有至關大的提高
一、原生方法較快——只要有可能,使用原生方法而不是本身用js重寫一個,由於原生方法是用注入C/C++之類的編譯型語言寫出來的,因此要比js快得多,好比要用Math對象的數學運算
二、Swithc語句較快——若是有一系列複雜的if-else語句,能夠轉換成單個switch語句則能夠獲得更快的代碼,還能夠經過將case語句按照最能夠能的到最不可能的順序進行組織,來進一步優化switch語句。
三、位運算符較快——當進行數學運算時,位運算符操做要比任何布爾運算或者算數運算快,諸如取模,邏輯與和邏輯或均可以考慮用位運算替換

以上內容參考自《JavaScript 高級程序設計(第三版)》終於寫完了(~ ̄▽ ̄)~

相關文章
相關標籤/搜索