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