1 設計原則
1.1 單一職責原則(SRP)
含義:每個對象/方法只做一件職責。
例子:單例模式下,創(chuàng)建div
做法:將獲取單例和創(chuàng)建div分離開來
實現(xiàn):
var getSingle = function(fn:Function) {
var result:any
return function () {
return result || (result = fn.apply(this,arguments))
}
}
var createDiv = function () {
var div = document.createElement('div')
div.innerHTML = '這個是單例模式創(chuàng)建的div'
document.body.appendChild(div)
return div
}
var createSingleDiv = getSingle(createDiv)
var div1 = createSingleDiv()
var div2 = createSingleDiv()
div1 === div2 //true
好處:
- 降低了單個方法/對象的復雜性
- 有利于代碼的復用和單元測試
- 當一個職責發(fā)生變化時,不會影響到其他的職責。
職責分離原則:
- 職責之間相互獨立,不會相互影響
- 如果兩個職責總是同時發(fā)生變化,就沒必要去分離
- 如果在一起的幾個職責不會發(fā)生任何變化,就沒必要去分離
壞處:
- 增加代碼的復雜度
- 增大了對象之間相互聯(lián)系的難度
1.2 最少知識原則(LKP)
含義:一個軟件實體應當盡可能少地與其他實體發(fā)生相互作用,軟件實體包含:對象、類、模塊、系統(tǒng)、變量、函數(shù)等。
例子:中介者模式,商城購買,購買手機 ,可以選擇手機的顏色、購買數(shù)量、手機內存,當庫存充足,購買按鈕可點擊 ,當庫存不充足,按鈕不可點擊,并顯示庫存不足的信息
做法:輸入框和下拉框發(fā)生事件時,只需要通知中介者它們發(fā)生了改變,讓中介者來執(zhí)行接下來的行為。
實現(xiàn):
index.html
<body>
選擇顏色: <select id="colorSelect">
<option value="">請選擇</option>
<option value="red">紅色</option>
<option value="blue">藍色</option>
</select>
選擇內存: <select id="memorySelect">
<option value="">請選擇</option>
<option value="32G">32G</option>
<option value="16G">16G</option>
</select>
輸入購買數(shù)量: <input type="text" id="numberInput"/><br/>
您選擇了顏色: <div id="colorInfo"></div><br/>
您選擇了內存: <div id="memoryInfo"></div><br/>
您輸入了數(shù)量: <div id="numberInfo"></div><br/>
<button id="nextBtn" disabled="true">請選擇手機顏色和購買數(shù)量</button>
<script src="./main.js"></script>
</body>
main.js
var goods = { // 手機庫存
"red|32G": 3,
"red|16G": 0,
"blue|32G": 1,
"blue|16G": 6
};
Function.prototype.after = function (fn) {
var self = this;
return function () {
var ret = self.apply(this, arguments);
if (ret === 'nextSuccessor') {
return fn.apply(this, arguments);
}
return ret;
}
};
var colorCheck = function (obj,color,memory,number,stock,nextBtn) {
if (obj === colorSelect) { // 如果改變的是選擇顏色下拉框
colorInfo.innerHTML = color;
}
if(!color) {
nextBtn.disabled = true;
nextBtn.innerHTML = '請選擇手機顏色';
} else {
return 'nextSuccessor'
}
}
var memoryCheck = function (obj,color,memory,number,stock,nextBtn) {
if (obj === memorySelect) {
memoryInfo.innerHTML = memory;
}
if(!memory) {
nextBtn.disabled = true;
nextBtn.innerHTML = '請選擇內存大小';
} else {
return 'nextSuccessor'
}
}
var numCheck = function (obj,color,memory,number,stock,nextBtn) {
if (obj === numberInput) {
numberInfo.innerHTML = number;
}
if(!Number.isInteger(number - 0) || number <= 0) {
nextBtn.disabled = true;
nextBtn.innerHTML = '請輸入正確的購買數(shù)量';
} else if(number > stock){
nextBtn.disabled = true;
nextBtn.innerHTML = '庫存不足';
} else {
nextBtn.disabled = false;
nextBtn.innerHTML = '放入購物車';
}
}
var checkBtn = colorCheck.after(memoryCheck).after(numCheck)
var mediator = (function () {
var colorSelect = document.getElementById('colorSelect'),
memorySelect = document.getElementById('memorySelect'),
numberInput = document.getElementById('numberInput'),
colorInfo = document.getElementById('colorInfo'),
memoryInfo = document.getElementById('memoryInfo'),
numberInfo = document.getElementById('numberInfo'),
nextBtn = document.getElementById('nextBtn');
return {
changed: function (obj) {
var color = colorSelect.value, // 顏色
memory = memorySelect.value, // 內存
number = numberInput.value, // 數(shù)量
stock = goods[color + '|' + memory]; // 顏色和內存對應的手機庫存數(shù)量
checkBtn(color,memory,number,stock,nextBtn)
}
}
})();
// 事件函數(shù):
colorSelect.onchange = function () {
mediator.changed(this);
};
memorySelect.onchange = function () {
mediator.changed(this);
};
numberInput.oninput = function () {
mediator.changed(this);
};
好處:
- 減少了對象之間的依賴
原則:
- 盡可能地減少對象之間的交互,如果兩個對象之間沒必要直接通信,可以引入一個第三者對象來承擔兩個對象間的通信
- 當一個對象必須引用另一個對象的時候,讓對象只暴露必要的接口,讓對象之間的聯(lián)系限制在最小的范圍內(廣義)
壞處:
- 有可能增加一些龐大到難以維護的第三者對象
1.3 開放封閉原則(OCP)
含義: 軟件實體應該是可以擴展的,但不可以修改,軟件實體包含:對象、類、模塊、系統(tǒng)、變量、函數(shù)等。當需要修改一個程序的功能或者給這個程序增加新的功能時,可以使用增加代碼的方式,而不是修改代碼的方式。
例子:將一個數(shù)組映射為另一個數(shù)組
做法:映射的步驟是不變的(循環(huán)遍歷),映射的方法是可變的,將映射的方法放在回調函數(shù)中封裝起來
實現(xiàn):
var arrMap = function(arr,callback) {
var i = 0,
length = arr.length,
value,
ret = []
for(;i<length;i++) {
value = callback(i, arr[i])
ret.push(value)
}
return ret
}
var a = arrMap([1,2,3],function (i,n) {
return n * 2
})
console.log(a)//[2,4,6]
好處:
- 避免修改源代碼可能造成的副作用
- 降低維護源代碼的成本
- 將系統(tǒng)中穩(wěn)定的部分和容易變化的部分分離開來 ,方便了以后替換更改變化的部分
原則:
- 利用多態(tài)性(把做什么和誰去做分離開來),找出程序中將要發(fā)生變化的地方,將這些變化封裝起來
- 在不可避免修改的情況下,盡量修改相對容易修改的地方
2 編程技巧
2.1 面向接口編程
含義: 面向接口編程就是面向抽象編程,關注點從對象的類型上 轉移到對象的行為上,針對對象的超類型的抽象方法編程。
接口既指對象響應的請求的集合,同時也指一些語言提供的關鍵字,如java的interface,專門負責建立類與類之間的聯(lián)系
2.1.1 抽象類
- 當泡茶和泡咖啡都有將原料倒入水中的操作時,我們可以將泡茶和泡咖啡向上轉型為泡飲料(體現(xiàn)了對象的多態(tài)性)
- 在泡飲料的類中寫一個將原料倒入水中的抽象方法,同時讓從泡飲料繼承來的泡茶和泡咖啡的子類重寫將原料倒入水中的方法
abstract class Beverage {
abstract operation():void
}
class Tea extends Beverage{
operation() {
console.log('將茶包放入水中')
}
}
class Coffee extends Beverage {
operation() {
console.log('將咖啡放入水中')
}
}
2.1.2 接口
2.2的例子也可以將泡飲料抽象為一個接口:
interface Beverage {
operation: Function
}
class Tea implements Beverage{
operation() {
console.log('將茶包放入水中')
}
}
class Coffee implements Beverage {
operation() {
console.log('將咖啡放入水中')
}
}
2.2 代碼重構
2.2.1 提煉函數(shù)
如果函數(shù)中有一大段代碼可以被獨立出來,最好是將這段代碼放入另外一個獨立的函數(shù)中
例子:頁面加載完成后既要創(chuàng)建一個圓形,又要打印一些頁面的版權信息
做法:將創(chuàng)建圓形和打印版權信息分離開來
實現(xiàn):
window.onload = function () {
createCircle()
log()
}
function createCircle() {
var canvas = document.getElementById('canvas')
if(canvas.getContext) {
var ctx = canvas.getContext('2d')
ctx.fillStyle='red';
ctx.arc(20,20,20,0,2*Math.PI)
ctx.fill()
}
}
function log() {
console.log('版權所屬')
}
2.2.2 合并重復的條件片段
如果每個if else判斷里都執(zhí)行同一段代碼,可以將這段代碼寫入一個單獨的函數(shù),并且從判斷中抽離出來,寫在判斷語句結束后
例子:跳轉頁面,當當前頁面為非正整數(shù)時,跳轉到第一頁;當前頁面大于總頁數(shù)時,跳轉到最后一頁;其余情況,正常跳轉
做法:將跳轉頁面抽離出來,放在判斷語句之后
實現(xiàn):
function paging(currPage,totalPage) {
if(currPage <= 0) {
currPage = 1
} else if (currPage >= totalPage) {
currPage = totalPage
}
jump(currPage)
}
2.2.3 把條件分支語句提煉成函數(shù)
當一個判斷語句過長時,可以將該語句抽離成一個函數(shù)
例子:當當前活動為已開始并且用戶已經報名或者當前活動已結束時,用戶才可以在當前活動詳情下進行曬圖
做法:將判斷語句抽離出來
實現(xiàn):
function canPhoto (activityState,userState) {
return (activityState === '已開始' && userState === '已報名') || activityState === '已結束'
}
function photoActivity(activityState,userState) {
if(canPhoto(activityState,userState)) {
doSomething()
}
}
2.2.4 合理循環(huán)
如果有些語句做的是一些重復性的工作,可以將工作放入一個數(shù)組中,進行循環(huán)
例子:創(chuàng)建XHR對象(IE9以下的瀏覽器)
做法:將創(chuàng)建對象的參數(shù)放入數(shù)組中進行循環(huán)
實現(xiàn):
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();
2.2.5 用函數(shù)退出來代替嵌套條件分支
當條件嵌套太多層時,可以將這些條件盡可能地抽離成一個層級的條件分支,在進入一些條件分支時,可以讓函數(shù)立即退出
2.2.6 傳遞對象參數(shù)代替過長的參數(shù)列表
當參數(shù)列表過長時,可以考慮將參數(shù)放入一個對象中,這樣不用擔心參數(shù)的數(shù)量和順序
比如在篩選文件的時候,我們可以通過文件的創(chuàng)建時間、修改時間、類型、創(chuàng)建者、關鍵字、下載量等信息進行篩選,就可以將這些參數(shù)放在一個文件的對象中
2.2.7 盡量減少參數(shù)數(shù)量
當有的參數(shù)可以通過內部計算獲得的,盡量減少這些參數(shù),而是在函數(shù)內部直接通過計算獲取
比如,在繪制正方形的時候,傳入的參數(shù)有寬度、高度和面積,但是面積其實可以通過寬度和高度運算得來,因此傳入的參數(shù)中可以去掉面積這個參數(shù)
2.2.8 少用三目運算符
三目運算符增加了代碼的可讀性和可維護性,但是省略的代碼量忽略不計
a === b ? a : b === doc ? b : doc === 'text' ? doc : null
2.2.9 合理利用鏈式調用
鏈式調用在調試的時候很不方便,當一條鏈發(fā)生錯誤,要把這條鏈全部加拆開加上debugger和一些console才能定位到錯誤的位置
鏈式調用可以參考jquery
2.2.10 分解大類
將大類中的行為分解到粒度更細的對象中
2.2.11 用return退出多重循環(huán)
在需要中止多重循環(huán)時直接退出整個方法,將中止后要執(zhí)行的函數(shù)或者代碼放在return 的后面
例子:找到相加為15的兩個小于10的數(shù)
做法:雙重循環(huán)
實現(xiàn):
for(var i = 0; i < 10; i ++ ) {
for(var j = 0; j < 10; j ++) {
if( i + j === 15) {
return console.log(i,j)
}
}
}