JavaScript單一職責原則深入分析
單一職責原則
就一個類而言,應該僅有一個引起它變化的原因。在 JavaScript 中,需要用到類的場景并不太多,單一職責原則更多地是被運用在對象或者方法級別上,因此本節(jié)我們的討論大多基于對象和方法。
單一職責原則(SRP)的職責被定義為“引起變化的原因”。如果我們有兩個動機去改寫一個方法,那么這個方法就具有兩個職責。每個職責都是變化的一個軸線,如果一個方法承擔了過多的職責,那么在需求的變遷過程中,需要改寫這個方法的可能性就越大。
此時,這個方法通常是一個不穩(wěn)定的方法,修改代碼總是一件危險的事情,特別是當兩個職責耦合在一起的時候,一個職責發(fā)生變化可能會影響到其他職責的實現(xiàn),造成意想不到的破壞,這種耦合性得到的是低內(nèi)聚和脆弱的設(shè)計。
因此,SRP 原則體現(xiàn)為:一個對象(方法)只做一件事情。
設(shè)計模式中的SRP原則
SRP 原則在很多設(shè)計模式中都有著廣泛的運用,例如代理模式、迭代器模式、單例模式和裝飾者模式。
代理模式
通過增加虛擬代理的方式,把預加載圖片的職責放到代理對象中,而本體僅僅負責往頁面中添加 img
標簽,這也是它最原始的職責。
myImage
負責往頁面中添加 img
標簽:
const myImage = (function () { const imgNode = document.createElement('img'); document.body.appendChild(imgNode); return { setSrc: function (src) { imgNode.src = src; } } })();
proxyImage
負責預加載圖片,并在預加載完成之后把請求交給本體 myImage
:
const proxyImage = (function () { const img = new Image; img.onload = function () { myImage.setSrc(this.src); } return { setSrc: function (src) { myImage.setSrc('file:// /C:/Users/svenzeng/Desktop/loading.gif'); img.src = src; } } })(); proxyImage.setSrc('http:// imgcache.qq.com/music/photo/000GGDys0yA0Nk.jpg');
把添加 img
標簽的功能和預加載圖片的職責分開放到兩個對象中,這兩個對象各自都只有一個被修改的動機。在它們各自發(fā)生改變的時候,也不會影響另外的對象。
迭代器模式
我們有這樣一段代碼,先遍歷一個集合,然后往頁面中添加一些 div
,這些 div
的 innerHTML
分別對應集合里的元素:
const appendDiv = function (data) { for (let i = 0, l = data.length; i < l; i++) { const div = document.createElement('div'); div.innerHTML = data[i]; document.body.appendChild(div); } }; appendDiv([1, 2, 3, 4, 5, 6]);
這其實是一段很常見的代碼,經(jīng)常用于 ajax 請求之后,在回調(diào)函數(shù)中遍歷 ajax 請求返回的數(shù)據(jù),然后在頁面中渲染節(jié)點。
appendDiv
函數(shù)本來只是負責渲染數(shù)據(jù),但是在這里它還承擔了遍歷聚合對象 data
的職責。 我們想象一下,如果有一天 api 返回的 data
數(shù)據(jù)格式從 array
變成了 object
,那我們遍歷 data
的代碼就會出現(xiàn)問題,必須改成 for ( let i in data )
的方式,這時候必須去修改 appendDiv
里的代碼,否則因為遍歷方式的改變,導致不能順利往頁面中添加 div 節(jié)點。
我們有必要把遍歷 data
的職責提取出來,這正是迭代器模式的意義,迭代器模式提供了一種方法來訪問聚合對象,而不用暴露這個對象的內(nèi)部表示。
當把迭代聚合對象的職責單獨封裝在 each
函數(shù)中后,即使以后還要增加新的迭代方式,我們只需要修改 each
函數(shù)即可,appendDiv
函數(shù)不會受到牽連,代碼如下:
const each = function (obj, callback) { let value, i = 0, length = obj.length, isArray = isArraylike(obj); // isArraylike 函數(shù)這里未實現(xiàn) if (isArray) { // 迭代類數(shù)組 for (; i < length; i++) { callback.call(obj[i], i, obj[i]); } } else { for (i in obj) { // 迭代 object 對象 value = callback.call(obj[i], i, obj[i]); } } return obj; }; const appendDiv = function (data) { each(data, function (i, n) { const div = document.createElement('div'); div.innerHTML = n; document.body.appendChild(div); }); }; appendDiv([1, 2, 3, 4, 5, 6]); appendDiv({ a: 1, b: 2, c: 3, d: 4 });
單例模式
在JavaScript單例模式實現(xiàn)過一個惰性單例,最開始的代碼是這樣的:
const createLoginLayer = (function () { let div; return function () { if (!div) { div = document.createElement('div'); div.innerHTML = '我是登錄浮窗'; div.style.display = 'none'; document.body.appendChild(div); } return div; } })();
現(xiàn)在我們把管理單例的職責和創(chuàng)建登錄浮窗的職責分別封裝在兩個方法里,這兩個方法可以獨立變化而互不影響,當它們連接在一起的時候,就完成了創(chuàng)建唯一登錄浮窗的功能,下面的代碼顯然是更好的做法:
const getSingle = function (fn) { // 獲取單例 let result; return function () { return result || (result = fn.apply(this, arguments)); } }; const createLoginLayer = function () { // 創(chuàng)建登錄浮窗 const div = document.createElement('div'); div.innerHTML = '我是登錄浮窗'; document.body.appendChild(div); return div; }; const createSingleLoginLayer = getSingle(createLoginLayer); const loginLayer1 = createSingleLoginLayer(); const loginLayer2 = createSingleLoginLayer(); alert(loginLayer1 === loginLayer2); // 輸出: true
何時應該分離職責
SRP 原則是所有原則中最簡單也是最難正確運用的原則之一。
要明確的是,并不是所有的職責都應該一一分離。
一方面,如果隨著需求的變化,有兩個職責總是同時變化,那就不必分離他們。比如在 ajax 請求的時候,創(chuàng)建 xhr
對象和發(fā)送 xhr
請求幾乎總是在一起的,那么創(chuàng)建 xhr
對象的職責和發(fā)送 xhr
請求的職責就沒有必要分開。
另一方面,職責的變化軸線僅當它們確定會發(fā)生變化時才具有意義,即使兩個職責已經(jīng)被耦合在一起,但它們還沒有發(fā)生改變的征兆,那么也許沒有必要主動分離它們,在代碼需要重構(gòu)的時候再進行分離也不遲。
違反 SRP 原則
在人的常規(guī)思維中,總是習慣性地把一組相關(guān)的行為放到一起,如何正確地分離職責不是一件容易的事情。
一方面,我們受設(shè)計原則的指導,另一方面,我們未必要在任何時候都一成不變地遵守原則。在實際開發(fā)中,因為種種原因違反 SRP 的情況并不少見。比如 jQuery
的 attr
等方法,就是明顯違反 SRP 原則的做法。jQuery
的 attr
是個非常龐大的方法,既負責賦值,又負責取值,這對于 jQuery
的維護者來說,會帶來一些困難,但對于 jQuery
的用戶來說,卻簡化了用戶的使用。
在方便性與穩(wěn)定性之間要有一些取舍。具體是選擇方便性還是穩(wěn)定性,并沒有標準答案,而是要取決于具體的應用環(huán)境。
SRP 原則的優(yōu)缺點
SRP 原則的優(yōu)點是降低了單個類或者對象的復雜度,按照職責把對象分解成更小的粒度,這有助于代碼的復用,也有利于進行單元測試。當一個職責需要變更的時候,不會影響到其他的職責。
但 SRP 原則也有一些缺點,最明顯的是會增加編寫代碼的復雜度。當我們按照職責把對象分解成更小的粒度之后,實際上也增大了這些對象之間相互聯(lián)系的難度。
到此這篇關(guān)于JavaScript單一職責原則深入分析的文章就介紹到這了,更多相關(guān)JS單一職責原則內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 深入理解JavaScript系列(6):S.O.L.I.D五大原則之單一職責SRP
- JavaScript最少知識原則介紹與體現(xiàn)
- JavaScript面向?qū)ο笾叽蠡驹瓌t實例詳解
- JavaScript的數(shù)據(jù)類型轉(zhuǎn)換原則(干貨)
- 深入淺析JavaScript的API設(shè)計原則
- 深入理解JavaScript系列(22):S.O.L.I.D五大原則之依賴倒置原則DIP詳解
- 深入理解JavaScript系列(21):S.O.L.I.D五大原則之接口隔離原則ISP詳解
- 深入理解JavaScript系列(8) S.O.L.I.D五大原則之里氏替換原則LSP
- 深入理解JavaScript系列(7) S.O.L.I.D五大原則之開閉原則OCP
相關(guān)文章
Javascript自定義函數(shù)判斷網(wǎng)站訪問類型是PC還是移動終端
如果,能夠判斷出訪問Web網(wǎng)頁的類型(PC還是移動終端)。就可以解決許多絢麗多彩的 Flash效果出不來的問題2014-01-01個人總結(jié)的一些JavaScript技巧、實用函數(shù)、簡潔方法、編程細節(jié)
這篇文章主要介紹了個人總結(jié)的一些JavaScript技巧、實用函數(shù)、簡潔方法、編程細節(jié),本文講解了變量轉(zhuǎn)換、取整同時轉(zhuǎn)換成數(shù)值型、日期轉(zhuǎn)數(shù)值、類數(shù)組對象轉(zhuǎn)數(shù)組、進制之間的轉(zhuǎn)換等方法技巧,需要的朋友可以參考下2015-06-06JavaScript函數(shù)之call、apply以及bind方法案例詳解
這篇文章主要介紹了JavaScript函數(shù)之call、apply以及bind方法案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08