【前言】
最近閱讀了《JavaScript設計模式與開發實踐》,收貨頗豐,於是想寫一點總結及感想
寫一篇文章對於我的意義在於:
給別人講述知識時可以發現自己掌握的是否牢固透徹,寫的過程不斷髮現自己的不足,然後通過一些方式來解決問題,這也是一種學習過程;當然,寫文章給別人看,也要從讀者的角度出發,考慮他們想要從這篇文章獲得什麼,還有就是你想表達些什麼給讀者
這種過程大概叫費曼學習法,圖解:
(圖片來自網路,侵刪)
這篇文章我想表達的是:學習設計原則設計模式的好處、介紹設計原則和設計模式、常用設計模式的實踐、程式碼重構的具體方法、一些問題一些思考。你可以先讀一遍帶著疑問去閱讀這本書籍或者閱讀完書籍再來看這篇文章是否有助於你理解
一、為什麼要學習設計原則、設計模式
首先,設計原則、設計模式受用的目標人群我覺得是有一定的js基礎且有一定的專案實踐經歷的開發者,不然的話,就算學習設計也是生搬硬套,收貨甚微,當有了一定基礎及實踐之後,閱讀本書之後有三種感覺:
- 你的某些程式碼就是書上的反例,醍醐灌頂的感覺
- 你的某些程式碼已經實踐了某些設計模式遵從了某些設計原則,但是你並不知道這樣寫程式碼是叫這個模式以及這個模式的全部優缺點或者你的程式碼還有更進一步優化的空間
- 內心冷笑一聲:哼 so easy... emmmmm,如果大佬還願意繼續閱讀本文的話,希望大佬可以在評論區指點一二
個人認為,JavaScript設計原則以及設計模式都只屬於軟體設計的一部分,但這意味著已經開始脫離了’API呼叫工程師‘的稱號,開始接觸程式設計思想,但是設計原則跟模式有限,只針對於程式碼層面。
打個比方:vue原始碼使用了xx模式,解決了xx問題,但是,在選擇xx模式解決xx問題的背後又有多少思考?呢?
簡單猜測一下:一個框架or庫 = 軟體設計 + 其他框架優點借鑑 + 創新 + 編碼 + 測試;
本人水平有限,這是知識巨集觀的揣測一下實現一個框架or庫需要的付出。請不要當真or較真
可能沒人要你去寫框架什麼的,但是你負責的部分總是你來寫來維護,不按套路出牌受傷的是自己
舉這個例子是想說明:學習一下設計原則設計模式是多麼的有必要(強行解釋,最為致命)
二、JavaScript常用設計原則
我想提一個這本書的缺點,就是目錄,設計模式都是要遵循設計原則的,而且很多設計模式章節都提到了設計原則,然而書的目錄是最後一個大章節才說的設計原則,我覺得設計原則應該放在設計模式之前,所以建議先閱讀設計原則再閱讀設計模式,在理解上也會有幫助,至於第一章節的基礎知識介紹,這個看各人情況可以選擇要不要忽略
下面介紹常用的設計原則
1. 單一職責原則 SRP
【定義】
單一職責原則的定義是‘引起變化的原因’,如果你有兩個原因去修改一個方法,說明這個方法有兩個職責,承擔的職責越多你需要修改他的可能性就越大,修改程式碼總是件危險的事情,特別是兩個職責耦合在一起的時候
一句話概括:一個物件(方法)只做一件事
【理解】
書上有介紹單一職責原則相關的模式,其實我覺得從原則去聯絡模式有點不合理,因為所有模式都會去遵從設計原則,知識側重點不一樣而已。所以我會在下個章節的模式裡去聯絡原則,而說原則,我只想脫離模式單獨去說原則的優缺點以及如何應用
【優點】降低了單個類或者物件的複雜度,按照職責把物件分解成更小的粒度,這有助於程式碼的複用,也有利於進行單元測試。當一個職責需要變更的時候,不會影響到其他的職責。
【缺點】增加編碼複雜度,同時增加物件之間聯絡的難度
【應用】
煮個栗子,js的元件化開發,有一種思想就是元件的原子化,把元件拆到不能再拆的粒度,是最好維護的。但事實真的是這樣嗎,元件多了,元件之間的聯絡也多了,就意味需要管理更多的元件及元件間的複雜關係,有時候明明兩個元件像孿生兄弟一樣分不開,你偏要拆散他們並且給他們一座橋樑去聯絡,這無疑比讓他們在一起成為一個整體要難維護的多,而且顆粒度越小,代表著開發成本越大。
單一職責原則也是如此,這些思想其實建立在理想化的狀態上,而實際情況往往不會與理想對等,所以實際專案使用中,要學會靈活多變,知道什麼時候其實是可以違反原則的,借用書上的話:
SRP 原則是所有原則中最簡單也是最難正確運用的原則之一。要明確的是,並不是所有的職責都應該一一分離
一方面,如果隨著需求的變化,有兩個職責總是同時變化,那就不必分離他們。比如在 ajax 請求的時候,建立 xhr 物件和傳送 xhr 請求幾乎總是在一起的,那麼建立 xhr 物件的職責和傳送 xhr 請求的職責就沒有必要分開。
另一方面,職責的變化軸線僅當它們確定會發生變化時才具有意義,即使兩個職責已經被耦 合在一起,但它們還沒有發生改變的徵兆,那麼也許沒有必要主動分離它們,在程式碼需要重構的 時候再進行分離也不遲
個人認為這是理解最簡單,實踐最難的一個
// bad
var createLoginLayer = (function(){
var div;
return function(){
if ( !div ){
div = document.createElement( 'div' );
div.innerHTML = '我是登入浮窗';
div.style.display = 'none';
document.body.appendChild( div );
}
return div; }
})();
// good
var getSingle = function( fn ){ // 獲取單例
var result;
return function(){
return result || ( result = fn .apply(this, arguments ) );
}
var createLoginLayer = function(){ // 建立登入浮窗
var div = document.createElement( 'div' );
div.innerHTML = '我是登入浮窗';
document.body.appendChild( div );
return div;
};
var createSingleLoginLayer = getSingle( createLoginLayer );
var loginLayer1 = createSingleLoginLayer();
var loginLayer2 = createSingleLoginLayer();
alert ( loginLayer1 === loginLayer2 ); // 輸出: true
2. 最少知識原則 LKP
單一職責原則說道:一個物件(方法)只做一件事;那代表著我們要建立更多的物件(方法)來分解一個之前比較大的物件(方法),那分解之後,物件(方法)是小了,好維護好擴充套件了,但是物件之間的聯絡缺越來越多了,有兩個物件非常耦合,怎麼辦
【定義】
最少知識原則要求我們在設計程式時,應當儘量減少物件之間的互動。如果兩個物件之間不 必彼此直接通訊,那麼這兩個物件就不要發生直接的相互聯絡。常見的做法是引入一個第三者對 象,來承擔這些物件之間的通訊作用。如果一些物件需要向另一些物件發起請求,可以通過第三 者物件來轉發這些請求
【優點】減少或消除物件之間的耦合程度,提高複用性
【缺點】需要封裝物件或者引入一個第三者物件來處理兩者之間的聯絡,有時候第三者物件會複雜到難以維護
【應用】封裝是廣義上最少知識原則的一種體現,封裝的好處是隱藏內部屬性、方法、實現細節,只丟擲指定資料或物件給外界,外界使用封裝物件的物件越少說明聯絡就越少。另外一種就是兩個物件很耦合的時候通過引入第三者來處理兩個物件之間的關係
這個demo是我自己編的,大概的意思是一個班級有n個學生,學生有自己的年齡,年齡相等的學生可以做朋友(emmmm,原諒我奇葩的腦洞,還好我沒說性別一樣的才能做朋友,不然要被在座的各位打屎),如果每次新進來一個學生,都要全班一個學生一個學生的問年齡,那太麻煩了,如果老師入學的時候把學生按年齡分批了,那是不是很好找到,這個按年齡分批次就是引入的第三者來處理這個問題,我不知道夠不夠形象啊,但是書上的外觀模式的程式碼舉例真的辣雞,不夠形象?
let nameList = [{
name: 'eason',
age: 1
}, {
name: 'taylor',
age: 3
}, {
name: 'jack',
age: 2
}, {
name: 'yu',
age: 1
}, {
name: 'xixi',
age: 3
}]
let state = {}
let People = (name, age) => {
let that = {}
that.name = name
that.age = age
that.friends = []
return that
}
// bad
for (let n of nameList) {
state[n.name] = new People(n.name, n.age)
}
let jay = new People('jay', 3)
let syz = new People('syz', 2)
let keys = Object.keys(state)
for (let k of keys) {
if (state[k].age === jay.age) {
jay.friends.push(state[k].name)
}
if (state[k].age === syz.age) {
syz.friends.push(state[k].name)
}
}
console.log('jay-friends', jay.friends) // ["jay", "taylor", "xixi"]
//good
let ageList = []
let ageMap = {}
for (let n of nameList) {
state[n.name] = new People(n.name, n.age)
if (ageList.indexOf(n.age) < 0) {
ageList.push(n.age)
ageMap[n.age] = []
ageMap[n.age].push(n.name)
} else {
ageMap[n.age].push(n.name)
}
}
let addPeople = (name, age) => {
ageMap[age] = ageMap[age] || []
ageMap[age].push(name)
return new People(name, age)
}
let jay = addPeople('jay', 3)
let syz = addPeople('syz', 2)
console.log('jay-friends', ageMap[jay.age]) ["taylor", "xixi", "jay"]
console.log('syz-friends', ageMap[syz.age]) ["jack", "syz"]
3. 開放-封閉原則 OCP
【定義】
當需要改變一個程式的功能或者給這個程式增加新功 能的時候,可以使用增加程式碼的方式,但是不允許改動程式的原始碼
我來強行解釋一下,對擴充套件開放,對修改封閉
【優點】程式的穩定性提升、容易變化的地方分離出來後更容易維護
【缺點】程式碼的完全封閉幾乎不可能,誰也沒有’未卜先知‘的能力,但是我們可以儘可能的去容易變化和不容易變化的地方,挑出容易變化的地方進行封閉
【應用】
- 用物件的多型性消除條件分支(常用)
多型指的是:同一操作在不同的物件上展現出不同的結果
多型最根本的作用就是通過把過程化的條件分支語句轉化為物件的多型性,從而消除這些條件分支語句
我的理解是,只要有類似的行為(map.show instanceof Function)就可以去執行然後呈現出不同的結果,不必每個物件的’型別‘
程式碼對比:
// bad
var googleMap = {
show: function(){
console.log( '開始渲染谷歌地圖' ); }
};
var baiduMap = {
show: function(){
console.log( '開始渲染百度地圖' ); }
};
var renderMap = function( type ){
if ( type === 'google' ){
googleMap.show();
}else if ( type === 'baidu' ){
baiduMap.show();
} };
renderMap( 'google' ); // 輸出:開始渲染谷歌地圖
renderMap( 'baidu' ); // 輸出:開始渲染百度地圖
// good
var renderMap = function( map ){
if ( map.show instanceof Function ){
map.show(); }
};
renderMap( googleMap ); // 輸出:開始渲染谷歌地圖
renderMap( baiduMap ); // 輸出:開始渲染百度地圖
- 封裝變化
封裝變化就是把可能變化的地方分離出去,這樣只需要維護已經被分離出去的容易變化的地方
方式有:放置掛鉤、回撥函式等
原諒我懶...
- 此原則的相對性
總結:開放-封閉原則其實還是跟我們的程式的維護及擴充套件性相關的原則,設計原則基本是圍繞程式的可讀、可維護、可擴充套件、高效能、安全等方面來進行的(正確性有待考究,目前我是這麼認為的,有不同意見歡迎評論區留言),設計原則這塊說的都比較相對,就想上一張的最少知識和單一職責原則一樣,實際專案想要的做到這些,只能是一定程度上的,因為有些原則就像一個主觀題,是沒有標準答案的,但是有跡可循
三、常用設計模式
從【定義】【理解】【應用】【優點】【缺點】五個維度來介紹這些設計模式及應用
1. 單例模式
- 【定義】
保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點
- 【理解】
從字面意思來看,單例意思是單個例項。從定義來看,一個類只有一個例項,但是對於JavaScript來說創造一個物件so easy,不必像別的語言一樣必須先建立一個類再通過類建立物件,所以定義第一句對於js來說無異於脫了褲子放p,我們只需要保證一個唯一的物件即可。(很多書籍都是比較早,在ES6之前發行的,所以都沒有類這一說,所以所有JavaScript關於js無類這一塊,大家可以自行思考,有類的情況下應該是怎樣的)
為什麼只能有一個例項?將全部職責集中在一個例項上(這種物件往往對比普通物件比較龐大複雜),這樣就能避免無謂的浪費
為什麼要提供一個全域性的訪問點?因為只有一個例項啊,所以要保證想用的地方都能訪問到
- 【應用】
個人覺得只要是那種使用相對頻繁、功能強大、建立一次相對消耗效能多的都可以考慮使用單例模式,這裡圈起來,要考!別以為自己有個有點複雜的物件就想說使用單例模式,你可以先看下window物件有多少屬性多少方法
還有一點就是,因為唯一的,而且建立消耗一點效能,我們可以在需要的時候去判斷例項是否存在,不存在再建立,存在就直接使用,這種叫做惰性單例,比如xhr
- 【優點】
唯一例項,節約記憶體開銷;
- 【缺點】
違背’單一職責原則‘,不好維護、不容易擴充套件
不適用於容易變化的例項
2. 代理模式
- 【定義】
代理模式是為一個物件提供一個代用品或佔位符,以便控制對它的訪問
- 【理解】
明星有經紀人作為代理,自身太忙無暇處理or不方便露面or...的事物都由經紀人代為處理,明星可以跟代理人溝通(我要吃冰淇淋,我要演這個不演那個balabala),程式設計中的代理也是如此。
代理分保護代理和虛擬代理,但是js中保護代理不容易實現,以為常用的是虛擬代理
- 【應用】
惰性載入,比如圖片懶載入
快取代理,比如http304、Memoization,不懂Memoization的可以移步我的JavaScript效能優化--演算法和流程控制章節
- 【優點】
分解本體職責--單一職責原則;解決本體暫時性無法處理一些請求;節約效能
- 【缺點】
編碼需要維護代理與本體的聯絡
編碼需要保持代理和本體介面的一致性
3. 策略模式
- 【定義】
定義一系列的演算法,把它們一個個封裝起來,並且使它們可以相互替換
- 【理解】
把分支轉成配置,利用配置引數可以減少很多分支
當你的程式碼裡有很多if else或者switch的時候,就該考慮一下策略模式是否可以替換那些分支了
為什麼要用策略模式替換那些分支,一般情況下,我們把某個職責委託給一個策略物件,策略物件下的每一個鍵值對是一個分支,命名規範的話,key也是一個很好的註釋,物件比分支更易讀易擴充套件程式碼量更少
回憶一下開發-封閉的設計原則:對擴充套件開放,對修改封閉,把容易改變的地方分離出來;策略物件就是被分離出來的容易被改變的地方
demo:
從上海出發到倫敦
// bad
if (money === 100) {
way = train()
} else if (money === 200) {
way = car()
} else if (money === 300) {
way = ship()
} else if (money === 400) {
way = flight()
}
// good
const wayMap = {
m100: train(),
m200: car(),
m300: ship(),
m400: flight()
}
- 【應用】
表單校驗、演算法分類、其他選擇性較多且目標相同的需求
- 【優點】
避免多重條件選擇語句
遵循開發封閉原則,程式碼更易閱讀、維護和擴充套件,一定程度上減少程式碼數量
提高可複用性
- 【缺點】
違背最少知識原則,使用策略物件處耦合性大
4. 狀態模式
- 【定義】
狀態模式的關鍵是區分事物內部的狀態,事物內部狀態的改變往往會帶來事物的行為改變
- 【理解】
狀態模式跟策略模式在實現上有一定的相似之處,但是目的不一樣,策略模式每條策略都是平行平等的,而狀態模式最大的區別是所有狀態都是預先定義好的
煮個栗子?:電風扇的1-5擋是狀態模式,1-5擋是預先定義好的,結果是風力大小不一樣,從上海到倫敦旅遊,選擇的交通方式是策略模式,坐汽車、火車、飛機、船是平行的,結果是你到了倫敦
改變行為發生在狀態模式內部,而策略模式是外部選擇控制的,使用者也不需要了解狀態模式的內部細節
也就是說狀態模式是遵循最少知識原則的
- 【應用】
狀態機,把狀態抽離出來單獨封裝在一個物件裡面,直觀方便,只需call(狀態機)一下使用
demo:
每個年齡段都要去做相對應的事
// bad
if (age === 7) {
work = primarySchool()
} else if (age === 13) {
work = juniorMiddleSchool()
} else if (age === 16) {
work = highSchool()
} else if (age === 19) {
work = university()
} else if (age === 23) {
work = growUP()
} else if (age > 24) {
work = makeMoney()
}
// good
const workMap = {
age7: primarySchool(),
age13: juniorMiddleSchool(),
age16: highSchool(),
age19: university(),
age23: growUP(),
ageMore: makeMoney(),
}
- 【優點】
容易增加新狀態或者切換當前狀態
請求動作跟狀態中封裝的行為解耦
易維護擴充套件
- 【缺點】
邏輯分散不易讀,狀態多比較枯燥(if else更枯燥?)
5. 享元模式
- 【定義】
享元(flyweight)模式是一種用於效能優化的模式
享元模式的核心是運用共享技術來有效支援大量細粒度的物件
- 【理解】
為什麼說是一種效能優化模式?很多人喜歡在for迴圈裡new一個物件,然後利用迴圈物件的當前值去改變這個物件再去寫一些邏輯,其實很多時候這些物件改變之後還是有很多的相似之處的,享元模式就是把這些在迴圈裡建立的物件提取到迴圈外層建立一次,然後迴圈內每次做一定改動再去寫xx邏輯(這裡只是舉例迴圈,享元模式可以在很多地方使用不限於迴圈)
為了幫助理解(其實我覺得這個模式很好理解),書上舉例說:有100種衣服,讓100個模特一人試一件 和 一個模特試100件,假如請一個模特是100塊(建立物件消耗),你願意請多少個模特,一個模特試100件衣服肯定比100個模特慢,所以,享元模式是一種利用時間換取空間的優化模式
一個測試:
像let const 這種塊級作用域,程式碼塊執行完會立即釋放變數記憶體的
let aaa = () => {
debugger
for (let i = 0; i < 4; i++) {
let x = new Object({name: i})
console.log(x)
}
}
aaa() // {name: 0} {name: 1} {name: 2} {name: 3}
let aaa = () => {
debugger
for (let i = 0; i < 4; i++) {
let x = {name: i}
console.log(x)
}
}
aaa() // {name: 3} {name: 3} {name: 3} {name: 3}
這兩種其實是一樣,除了物件建立的方式不同,其他一樣,對比是讓你明白,第一種方式也會建立一個新的物件,x指標的指向是新建立的物件的引用地址
- 【應用】
如果考慮需要複用元物件的話,建議定義好元物件,然後使用的地方深拷貝或者淺拷貝一下再使用,這樣其他地方也可以使用而不破壞元物件啦
let obj = {
name: 'xx',
age: 0,
aaa: 111,
bbb: 222,
ccc: 333
}
let a = Object.assgin({}, obj)
// let a = JSON.parse(JSON.stringify(obj))
let aaa = key => {
console.log(key.age)
}
for (let i = 1; i < 5; i++) {
a.age = i
aaa(a)
}
其實我覺得享元模式跟單例模式有一定的相似之處的,雖然書上沒提,但我真覺得這倆模式真的很像,單例模式被copy一下就是享元模式了,但是單例還是有特殊之處的,所有的狀態屬性方法都歸於一個物件or例項上,並且提供全域性的訪問介面,想想都很強大,但是也比較難以維護
適用性:
- 【優點】
都說了是一種效能優化模式了,那有點肯定就是效能優化了,解決大量物件帶來的效能問題
- 【缺點】
缺點就是你要花時間學習並理解它 --哈撒給
6. 職責鏈模式模式
- 【定義】
使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之間的耦合關係,將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。
- 【理解】
打個比方,有個魔方,100個人裡面有10個人會解這個魔方,現在魔方在你手上,你不知道誰會解魔方,但是你知道某幾個人可能會解魔方,於是你把魔方傳給A,A不會再傳給B(被傳的都是已知有可能會解魔方的人)直到有人解開魔方
某些場景下你需要費很大力氣才能建立請求者和接收者之間的聯絡,這樣使兩者直接需要很大的耦合性,而使用職責鏈模式可以解除這種耦合
作用域、原型鏈、事件冒泡跟職責鏈是不是很相似,個人覺得跟promise也有點相似,從介面獲取到資料然後一層一層的promise去篩選過濾最後達到業務層,業務層拿到資料處理業務邏輯or呈現資料
- 【應用】
管理程式碼、其他類似事件冒泡的需求,說實話 我幾乎沒用過這個模式
- 【優點】
降低發起請求的物件和處理請求的物件之間的耦合性
- 【缺點】
鏈條太長對效能有較大影響
7. 裝飾者模式
- 【定義】
裝飾者模式可以動態地給某個物件新增一些額外的職責
裝飾者模式能夠在不改變物件自身的基礎上,在程式執行期間給物件動態地新增職責
- 【理解】
鋼鐵俠撿到了滅霸的手套?並打了個響指
- 【應用】
demo:
var plane = {
fire: function(){
console.log( '發射普通子彈' ); }
}
var missileDecorator = function(){ console.log( '發射導彈' );
}
var atomDecorator = function(){ console.log( '發射原子彈' );
}
var fire1 = plane.fire;
plane.fire = function(){ fire1();
missileDecorator(); }
var fire2 = plane.fire;
plane.fire = function(){ fire2();
atomDecorator(); }
plane.fire();
// 分別輸出: 發射普通子彈、發射導彈、發射原子彈
使用AOP裝飾函式
把這段看懂就ok了
- 【優點】
擴充套件性好、隨變化而變化,靈活、甚至預測需求
- 【缺點】
暫時沒想到...
8. 釋出-訂閱模式模式(觀察者模式)
- 【定義】
發布—訂閱模式(其實也有一種叫觀察者模式的模式,跟這個很像,但是沒有中間的排程層),它定義物件間的一種一對多的依賴關係,當一個物件的狀 態發生改變時,所有依賴於它的物件都將得到通知。在 JavaScript 開發中,我們一般用事件模型 來替代傳統的釋出—訂閱模式
- 【理解】
Vue的Bus通訊就是一種釋出-訂閱模式,中間的vue例項就是排程層,但是這個必須得先監聽再推送,推送過的再新增監聽就監聽不到了
而觀察者模式指的是觀察者與被觀察者之間直接的通訊
- 【應用】
釋出者的每次釋出可以快取起來,等有新的訂閱者時,再把之前快取的訊息一起傳送給新的訂閱者,當然這只是有這個需求才這麼做,沒有的話就不用快取了
現在流行的MVVM框架原始碼都有用到釋出-訂閱模式
addEventListener,新增事件監聽
- 【優點】
可以隨意增加或刪除訂閱者,這不會影響到釋出者的程式碼編寫,耦合低
- 【缺點】
消耗時間、記憶體、巢狀過深導致難以追蹤
四、程式設計技巧
1. 提煉函式
單一職責原則 如果一個函式過長,不得不通過若干註釋來顯得易讀,說明這個函式需要分解
好處:避免超大函式,程式碼複用,利用修改或覆蓋,良好的函式命名也是一種註釋
編碼規範:理論上函式不超過50行,最好不超過20行
2. 合併重複的條件片段
定義:條件分支語句存在重複的時候,有必要進行去重工作
demo:
// bad
var paging = function(currPage) {
if (currPage <= 0) {
currPage = 0
jump(currPage)
} else if (currPage <= 1) {
currPage = 1
jump(currPage)
} else {
jump(currPage)
}
}
// good
var paging = function(currPage) {
if (currPage <= 0) {
currPage = 0
} else if (currPage <= 1) {
currPage = 1
}
jump(currPage)
}
3. 把條件分支語句提煉成函式
複雜的條件語句是難讀的
可以把這句程式碼提煉成一個單獨的函式,既能更準確地表達程式碼的意思, 函式名本身又能起到註釋的作用
我加一句,如果需要修改條件的話,這樣寫也是更容易維護的
4. 合理使用迴圈
對於重複性工作,建議使用迴圈
比如:把key都放在一個陣列裡面,然後迴圈陣列,new個key就可以了
有時候新增修改刪除key等很方便
5. 提前讓函式退出代替巢狀條件分支
一個函式只有一個入口,但是可以有多個出口
所以不必把所有的邏輯進行完,最後return,如果不關注剩下的邏輯,在滿足條件的時候就可以return,這樣做的目的不是程式少執行了哪幾步,而是程式碼更簡潔清晰易讀
6. 傳遞物件引數代替過長的引數列表(物件導向程式設計--程式設計模式--配置物件)
這個在物件導向程式設計的程式設計模式章節有說,是程式設計模式的一個case,叫做配置物件,也就是把若干個引數放到到一個物件裡面,成了一個集合,這個物件可以叫做配置物件
- 不用考慮引數的順序
- 可以跳過某些引數的設定
- 函式擴充套件性更強,可以適應將來的擴充套件需要
- 程式碼可讀性更好,因為在程式碼中我們看到的是配置物件的屬性名稱
7. 儘量減少引數數量
一句話:不要傳沒必要的引數,如果是備著以後擴充套件的話,等到擴充套件的時候再加也不遲
8. 少用三目運算子(三元運算子)
有一些程式設計師喜歡大規模地使用三目運算子,來代替傳統的 if、else。理由是三目運算子性 能高,程式碼量少。不過,這兩個理由其實都很難站得住腳(這是書上原話)
關於簡潔性,三元運算子絕對比if else簡潔,但是要分場合使用,條件分支邏輯簡單的時候建議用三目運算子,但是條件分支邏輯複雜的時候,易讀跟維護性都是很差的,比如:
對於效能,我做了個簡單的測試,如圖,0.2ms 除以 百萬 是可以忽略不計的,所以效能上幾乎沒差的
9. 合理使用鏈式呼叫
書上說的鏈式呼叫(類似這個:console.log( new User().setId( 1314 ).setName( 'sven' ) );)節約的位元組數量微不足道,鏈式呼叫的壞處就在不好除錯上,我看chrome上一般都把錯誤一層一層的細化,然後我測試了一下這種鏈式呼叫會不會具體到其中一個方法,結果是不會,如圖;(以前編碼肯定也遇到過鏈式呼叫報錯,但是沒注意到這個細節,作者連除錯難度都考慮到了,在下跪拜...)
如果該鏈條的結構相對穩定,後期不易發生修改,那麼使用鏈式呼叫無可厚非。但如果該鏈 條很容易發生變化,導致除錯和維護困難,那麼還是建議使用普通呼叫的形式
一句話總結:穩定的鏈條可以寫,不穩定的不要寫
10. 分解大型類
這個我不太想贅述,感覺很多的原則or模式都有點似曾相識的感覺,這個一看不還是單一職責原則,大拆小,小的組合成大的,到底還是為了單個小功能的可讀可維護擴充套件等等更好
11. 用return退出多重迴圈
假設在函式體內有一個兩重迴圈語句,我們需要在內層迴圈中判斷,當達到某個臨界條件時退出外層的迴圈。我們大多數時候會引入一個控制標記變數
這玩意我以前好像就是這麼處理的,引入一個標誌位,外層迴圈每次都判斷這個標誌位,符合條件就結束迴圈
11. 結束語
整篇寫完自己對設計原則和部分設計模式有了更深的瞭解,文中有不當之處歡迎評論區指正,
寫完我有個反思,有的時候寫東西喜歡叨叨,生怕別人沒看到沒看懂,以後一定精簡
碼字不易,您的【點贊】是我堅持的最大動力~
【注】轉載請註明作者和出處~謝謝合作!