JS最佳實踐——紅皮書

前言

《Javascript高級程序設計》最佳實踐

1 可維護性

  • 可理解性
  • 直觀性
  • 可適應性
  • 可擴展性
  • 可調試性

總結

要做到可維護性、可讀性強的代碼,至少完成以下幾點

  1. 函數註釋(功能、參數、返回值)

    /**
     * 功能描述
     *
     * @param {參數類型}參數名 參數說明 
     * @return {返回值類型} 返回值說明
     */
    function fn(p1, p2) {
        // 參數類型:fn、Number、String、Boolean、Array、Object
    }
    
  2. 完成獨立功能的大段代碼,寫功能註釋

  3. 有意義的變量名、函數名,駝峯命名

2 降低耦合

耦合:兩個模塊之間的輸入與輸出的聯繫緊密,相互影響過多

代碼耦合過緊會導致模塊難以維護,修改一處的同時另一處也需要修改,降低了我們的開發效率。

2.1 將css從js中抽離

// bad
element.style.color = '#FFF';
element.style.width = '30px';
element.sytle.display = 'block';

// good
.show { // css
    color: #FFF;
    width: 30px;
    display: block;
}
element.className += 'show';

2.2 模板文本寫註釋

需要JS動態生成的HTML內容,在父元素下寫上模板的註釋,易於後期維護

<ul id="mylist">
    <!-- <li><a href="1">First item</a></li> -->
</ul>

2.3 應用邏輯 / 事件處理程序分離

2.3.1 概念

什麼是事件處理程序

事件就是用戶或瀏覽器自身執行的某種動作。比如說 click,mouseover,都是事件的名字。而相應某個事件的函數就叫事件處理程序

什麼是應用邏輯

被事件處理函數觸發的一些動作,比如對元素的顏色、寬度等屬性進行操作

爲什麼要分離

  • 方便更改觸發事件的方式,比如從鼠標點擊事件切換成按鍵事件
  • 可以直接測試應用邏輯代碼,無需通過特定事件

2.3.2 Demo

按下Enter鍵,div移動

document.onkeydown = function handle1(event) {
    if (event.keyCode === 13) {
        div.style.left = 100 + 'px';
        div.style.top = 100 + 'px';
    }
}

一般這樣寫並沒有什麼問題

加功能:點擊document對象,div移動到鼠標位置

document.onclick = function handle2(event) {
    div.style.left = event.clientX + 'px';
    div.style.top = event.clientY + 'px';
}

這樣裏面的代碼就重複了

分析一下代碼

// handle1是事件處理函數,判斷是否是Enter鍵按下
document.onkeydown = function handle1(event) {
    if (event.keyCode === 13) {
        // 下面是應用邏輯的代碼,控制div移動
        div.style.left = 100 + 'px';
        div.style.top = 100 + 'px';
    }
}

把應用邏輯的代碼單獨封裝成一個函數

// 兩個事件處理函數
document.onkeydown = function(event) {
    if (event.keyCode === 13) {
        moveFn(100, 100);
    }
};
document.onclick = function(event) {
    moveFn(event.clientX, event.clientY);
}
// 應用邏輯
function moveFn(x, y) {
    div.style.left = x + 'px';
    div.style.top = y + 'px';
}

注意:事件處理函數在傳值給應用函數時,應該event中所需要的數據,不要傳整個event對象。這樣做的好處是表明了應用邏輯所需要的具體參數,也便於測試人員理解函數的功能。

2.4 鬆散耦合原則

  • 函數自身的event對象,不能傳遞給其他函數
  • 應用邏輯中的動作,應該可以在不執行任何事件處理的情況下進行
  • 任何事件處理程序都應該處理事件,任何將處理交給應用邏輯

3 編程實踐

3.1 不輕易修改對象

除了你自己創建 / 維護的對象,原生對象、別人創建的對象一律不能修改

  1. 不能修改是指
  • 不爲實例 / 原型添加屬性
  • 不爲實例 / 原型添加方法
  • 不重複定義已存在的方法
  1. 爲什麼不能修改別人的對象
  • 例如對原生的Array對象添加一個aaa方法,當將來某一天Array原生支持了aaa方法,這樣你以前測試完善的代碼就會出錯
  1. 如果需要修改別人的對象
  • 繼承該對象,對繼承了的對象進行修改
  • 重新創建一個

3.2 避免全局量

let name = "yh";
function sayName() {
    console.log(name);
}

兩個全局量,name & sayName()方法,name還覆蓋了window.name的原生屬性

let mySpace = {
    name: 'yh',
    sayname: function() {
		console.log(this.name);
    }
}

一個全局量,而且屬性和方法有單獨命名空間,不用擔心和別人衝突

3.3 避免與null比較

如果看到與null比較的代碼,用以下方法替換:

  • 如果值爲引用類型,instanceof檢查其構造函數
  • 如果值爲基本類型,typeof檢查類型
  • 如果想確定對象是否有某個方法,typeof 對象.方法名

3.4 使用常量

4 性能

4.1 注意作用域

  1. 避免全局查找
function updateUI() {
    let imgs = document.getElementsByTagName('img');
    for (let i = 0, len = imgs.length; i < len; i++) {
        imgs[i].title = document.title + 'image' + i;
    }
    let msg = document.getElementById('msg');
    msg.innerHTML = 'Update complete.';
}

這段代碼裏至少有三個document的查詢,用一個變量將document對象存起來,就可以減少全局查找的次數,提高性能

function updateUI() {
    let doc = document;
    let imgs = doc.getElementsByTagName('img');
    for (let i = 0, len = imgs.length; i < len; i++) {
        imgs[i].title = doc.title + 'image' + i;
    }
    let msg = doc.getElementById('msg');
    msg.innerHTML = 'Update complete.';
}

一個函數中多次用到的全局變量存爲局部變量總是沒錯的

  1. 避免with語句

4.2 避免不必要的屬性查找

算法複雜度

標記 名稱 操作
O(1) 常數 訪問變量、訪問數組的元素
O(log n) 對數 二分查找
O(n) 線性 遍歷數組所有元素、訪問對象上的屬性
O(n2) 平方

對象的多重屬性查找

let total = ele.style.width + ele.style.height;

查找了 2 + 2 次,效率低

let eleStyle = ele.style;
let total = eleStyle.width + eleStyle.height;

查找了 1 + 1 + 1 次,節省25%

在大的程序中進行這種改進,優化比較明顯

數字化的數組位置 vs 命名屬性(NodeList對象等),優先選擇數組位置

4.3 優化循環

1. 減值迭代

大多數循環從0開始,增加到特定值結束。如果從特定值開始,減小到0結束,效率會更高

2. 簡化終止條件

每次循環都會計算終止條件,終止條件越簡單越好

// 增值迭代
for (let i = 0; i < arr.length; i++) {
    process(arr[i]);
}
// 減值迭代
for (let i = arr.length - 1; i >= 0; i--) {
    process(arr[i]);
}
// 每次循環時,少進行了一次 arr.length 的查找,提高了性能

3. 簡化循環體

循環體執行次數是最多的,確保性能最優

4. 展開循環

Duff裝置

5. 避免雙重解釋

eval()、function構造函數、setTimeout的第一個參數是字符串時會發生雙重解釋

eval("alert('Hello world!')");
let sayHi = new Function("alert(''Hello world!')");
setTimeout("alert('Hello world!')", 500);

一般都不會這麼寫

4.4 最小化語句數

1. 多個變量同時聲明

// 不好
let	num = 0;
let arr = [1, 2, 3];
let str = 'haha';
let obj = new Date();

// 好
let num = 0,
    arr = [1, 2, 3],
    str = 'haha',
    obj = new Date();

2. 插入迭代值

例如:從arr中獲取第i個值以後,讓 i+1

// 不好
let name = arr[i];
i++;

// 好
let name = arr[i++];

3. 使用數組和對象字面量

// 不好
let values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;

let person = new Object();
person.name = 'yh';
person.age = 18;
person.sayName = function() {
	console.log(this.name);
};

// 好
let values = [123, 456, 789];

let person = {
    name: 'yh',
    age: 18,
    sayName: function() {
        console.log(this.name);
    }
}

4.5 優化DOM交互

1. 最小化DOM更新(createDocumentFragment 或 innerHTML)

let list = document.getElementById('myList'),
    item,
    i;

for (i = 9; i >= 0; i--) {
    item = document.createElement('li');
    list.appendChild(item);
    item.appendChild(document.createTextNode('Item' + i));
}

上面這段代碼添加了十個 li ,每次添加都有兩個DOM更新(appendChild, createTextNode)

一共觸發了20次DOM更新,非常消耗性能

// 優化
let list = document.getElementById('myList'),
    fragment = document.createDocumentFragment(),
    item,
    i;

for (i = 9; i >= 0; i--) {
	item = document.createElement('li');
    fragment.appendChild(item);
    item.appendChild(document.createTextNode('Item' + i));
}

createDocumentFragment是DOM節點,但並不在DOM樹中,向它添加元素不會引起頁面重繪

2. 使用事件代理

儘可能將時間委託給祖先節點,減少頁面上的事件處理程序

3. 減少HTMLCollection

什麼情況下會得到HTMLCollection:

  • getElementsByTagName()
  • 獲取元素的childNodes
  • 獲取元素的attributes
  • 獲取特定的元素集合時,如document.forms, document.images

如果要多次訪問,可以用變量保存,減少訪問次數

4.6 其他方法

1. 原生方法

原生方法是C/C++編寫,比js快的多

多看看JS的原生方法,例如Math對象中的複雜數學運算

2. Switch語句

switch語句比if-else更快,

按照 最可能執行到的 → 最不可能執行到的 順序進行排列

3. 位運算符較快

儘可能用位運算符替代,例如 取模、與、或 等運算

每次循環都會

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章