前端解讀面向切面編程(AOP)
前言
面向對象(OOP)作為經典的設計範式,對於我們來說可謂無人不知,還記得我們入行起始時那句經典的總結嗎-萬事萬物皆對象。
是的,基於OOP思想封裝、繼承、多態的特點,我們會自然而然的遵循模塊化、組件化的思維來設計開發應用,以到達易維護、可擴展、高復用的目的。
既然OOP這麽多優點,那麽經常被大家提起的面向切面編程(AOP)是什麽回事呢,下面我們就一起來看一下。
AOP定義
第一步還是要知道aop是什麽,先個來自維基百科的解釋:
面向側面的程序設計(aspect-oriented programming,AOP,又譯作面向方面的程序設計、觀點導向編程、剖面導向程序設計)是計算機科學中的一個術語,指一種程序設計範型。
側面的概念源於對面向對象的程序設計的改進,但並不只限於此,它還可以用來改進傳統的函數。
其從主關註點中分離出橫切關註點是面向側面的程序設計的核心概念。分離關註點使得解決特定領域問題的代碼從業務邏輯中獨立出來.
業務邏輯的代碼中不再含有針對特定領域問題代碼的調用,業務邏輯同特定領域問題的關系通過側面來封裝、維護.
這樣原本分散在在整個應用程序中的變動就可以很好的管理起來。
tip
確實有點那麽不太清晰,有點亂。不過在亂之前,我們可以選能理解的部分先看一下:
- 側面(也就是切面) 用來描述分散在對象、類或函數中的橫切關註點。
重點在這,分散在對象中的橫切關註點,可以猜一下是什麽,應該就是不同對象之間公用的部分
- 側面的概念源於對面向對象的程序設計的改進,它還可以用來改進傳統的函數.
AOP 顯然不是OOP的替代品,是OOP的一種補充。
- 從主關註點中分離出橫切關註點是面向側面的程序設計的核心概念。
具體到業務項目中來說,主關註點就是業務邏輯了。針對特定領域問題代碼的調用,就是AOP要關註的部分
簡而言之,AOP是針對業務處理過程中的切面(即非業務邏輯部分,例如錯誤處理,埋點,日誌等)進行提取.
它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果(目的是降低耦合)。
具體到實現來說就是通過動態的方式將非主關註點部分插入到主關註點(一般是業務邏輯中)
說了這麽多,可能不太明白,還是一起看代碼吧。
埋點場景
很普遍的這麽個場景,需要點擊按鈕之後進行信息上報。
假設我們有這麽個logger的工具,可以進行上報:
const logger = console.log
//引入即可使用
logger(‘按鈕被點擊了‘)
那麽,我們直接擼起來吧:
const doSomething = ()=>{
console.log(‘doSomething‘)
}
let clickHandler = ()=>{
logger(‘doSomething之前‘)
// n行代碼
doSomething()
logger(‘doSomething之後‘)
//n 行代碼
}
看起來也沒什麽的,簡單粗暴。
如果有30個按鈕,每個業務邏輯不同,都需要埋這個點(假設打點信息一致)。
我們30個函數裏面,都要手動寫這個方法的話,這也太坑爹了吧。
主要是與業務代碼嚴重耦合,哪天不小心動了點其他內容,手抖誤刪了,就gg了。
後續維護的時候,簡直噩夢。
仔細看一下,這不就是符合AOP的使用前提嗎,那麽試試AOP吧。
關註點劃分
根據前面提的,可以劃分下關註點。
主關註點 | 側關註點 |
---|---|
業務邏輯(doSomething) | 埋點信息 logger |
前面提到AOP關註的是步驟具體到例子來說其實就是插入logger的步驟。
插入時機無非時業務邏輯執行之前或者之後的階段。
具體實現起來也不那麽困難
實現思路
具體到js來說,由於語言本身的特性,天生就具有運行時動態插入邏輯的能力。
重點在於在原函數上增加其他功能並不改變函數本身。
畢竟函數可以接受一切形式的參數,當然函數也不例外了。
當傳入一個函數的時候,我們要對其操作的余地就很大了,
保存原函數,然後利用後續參數加上call或apply,就可以達到我們的目的。
此外為了給函數都增加一個屬性,我們在原型上操作就行了。
經典before或者after的實現
網上太多類似實現了,直接看代碼好了:
// action 即為我們的側關註點,即logger
Function.prototype.after = function (action) {
//保留當前函數,這裏this指向運行函數即clickHandler
var func = this;
// return 被包裝過的函數,這裏就可以執行其他功能了。
// 並且該方法掛在Function.prototype上,
// 被返回的函數依然具有after屬性,可以鏈式調用
return function () {
// 原函數執行,這裏不考慮異步
var result = func.apply(this, arguments);
// 執行之後的操作
action.apply(this,arguments);
// 將執行結果返回
return result;
};
};
// before 實現類似,只不過執行順序差別而已
Function.prototype.before = function (action) {
var func = this;
return function () {
action.apply(this,arguments);
return func.apply(this, arguments);
};
};
那麽我們使用AOP改造之後的代碼就如下了:
const doSomething = ()=>{
console.log(‘doSomething‘)
}
let clickHandler = ()=>{
// n行代碼
doSomething()
//n 行代碼
}
clickHandler = clickHandler.before(()=>{
logger(‘doSomething之前‘)
}).after(()=>{
logger(‘doSomething之後‘)
})
clickHandler() // 執行結果和預期一致
到這裏就實現了面向切面編程,我們的業務邏輯裏面只管業務本身,側關註點通過這種方式來動態引入,與主邏輯解耦,更加純凈、易於維護。
結束語
到這裏,簡單的AOP就介紹完成了。利用這種模式結合我們js本身的特性,可以嘗試更多的可能。
例如我們react中常見的HOC、es7的裝飾者模式、HOF等,很多時候不得不感嘆大牛們思想的精髓,會讓我們有種頓悟的感覺。本文拋磚引玉,共同學習啦,對自己是總結和提高,更希望能幫助到需要的小夥伴。更多文章請移步我的博客
參考文章
AllyTeam - 用AOP改善javascript代碼
深入淺出 Javascript Decorators 和 AOP 編程
前端解讀面向切面編程(AOP)