JavaScript使用AOP編程思想實現(xiàn)監(jiān)聽HTTP請求
AOP切面編程的概念
AOP這個概念來源于Java
的Spring
框架,是Spring
為了解決OOP(面向?qū)ο缶幊棠J剑┟鎸σ恍I(yè)務(wù)場景的限制而開發(fā)來的,下面就讓我用JavaScript
代替Java
來解釋一下AOP
比如我現(xiàn)在使用OOP
寫法新建一個讀取數(shù)據(jù)的業(yè)務(wù)組件,分別有讀取data
,更新data
,刪除data
三個方法:
class DataService { constructor(ctx) { this.ctx = ctx; } createData() { ctx.createData(); } updateData() { ctx.updateData(); } deleteData() { ctx.deleteData(); } }
對于每個接口,業(yè)務(wù)可能會有一些相同的操作,如日志記錄、數(shù)據(jù)檢驗、安全驗證等,那么代碼就會像下面這樣
class DataService { constructor(ctx) { this.ctx = ctx; } createData() { ctx.dataCheck(); ctx.createData(); } updateData() { ctx.dataCheck(); ctx.updateData(); } deleteData() { ctx.dataCheck(); ctx.deleteData(); } }
一個相同的功能在很多不同的方法中以相同的方式出現(xiàn),這樣顯然不符合編碼的簡潔性和易讀性。 有一種解決方法是使用Proxy
模式,為了保持我們的代碼中一個類只負(fù)責(zé)一件事的原則,新建一個新的類繼承于DataService
,在這個類中為每個方法都加上dataCheck
class CheckDataService extends DataService { constructor(ctx) { super(ctx) } createData() { ctx.dataCheck(); ctx.createData(); } updateData() { ctx.dataCheck(); ctx.updateData(); } deleteData() { ctx.dataCheck(); ctx.deleteData(); } }
這樣的做法缺點是比較麻煩,每個Proxy
中都要重復(fù)執(zhí)行父類的方法。
那么這時就有了AOP
,其基本原理是在Spring
中某個類的運行期的前期或者后期插入某些邏輯,在Spring
中可以通過注解的形式,讓Spring
容器啟動時實現(xiàn)自動注入,如下面代碼片段
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect // 聲明為切面 @Component // 讓Spring能夠掃描并創(chuàng)建切面實例 public class LoggingAspect { // 在UserService的每個public方法前執(zhí)行l(wèi)ogBefore @Before("execution(public * com.example.UserService.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Logging before the method executes"); } // 在UserService的每個public方法前執(zhí)行l(wèi)ogAfter @After("execution(public * com.example.UserService.*(..))") public void logAfter(JoinPoint joinPoint) { System.out.println("Logging after the method executes"); } } // UserService import org.springframework.stereotype.Component; @Component // 聲明為一個Spring管理的組件 public class UserService { public void login() { System.out.println("Doing some important work"); } }
但是JS
沒有在底層實現(xiàn)這些東西,所以我們只能自己改造。
在JavaScript中實現(xiàn)AOP
思路是這樣的,我們將某個需要切入的方法進行重寫,在重寫后的函數(shù)中切入相關(guān)邏輯就行了
/** * 重寫對象上面的某個屬性 * @param targetObject 需要被重寫的對象 * @param propertyName 需要被重寫對象的 key * @param newImplementation 以原有的函數(shù)作為參數(shù),執(zhí)行并重寫原有函數(shù) */ function overrideProperty( targetObject, propertyName, newImplementation, ) { if (targetObject === undefined) return // 若當(dāng)前對象不存在 if (propertyName in targetObject) { // 若當(dāng)前對象存在當(dāng)前屬性 const originalFunction = targetObject[propertyName] const modifiedFunction = newImplementation(originalFunction) // 把原本的函數(shù)傳入 if (typeof modifiedFunction == 'function') { targetObject[propertyName] = modifiedFunction } } }
先寫一個公共方法,去重寫對象上的某個屬性,這樣我們可以調(diào)用overrideProperty
去重寫任意對象上的任何方法。 現(xiàn)在用overrideProperty
重寫一下window
上的fetch
方法
overrideProperty(window, 'fetch', originalFetch => { return function (...args) { // 在fetch發(fā)起前做些什么 return originalFetch.apply(this, args).then((res) => { // 在fetch完成后做些什么 return res }) } })
可以看到我們第三個參數(shù)傳入一個函數(shù)并返回一個函數(shù),這個返回的函數(shù)就是重寫完成的fetch
方法,只要在項目初始化時調(diào)用overrideProperty
,那么以后調(diào)用fetch
時都會執(zhí)行。 是不是感覺這樣寫也挺麻煩的,我們換一種寫法:
function overrideProperty( targetObject, propertyName, context, ) { if (targetObject === undefined) return if (propertyName in targetObject) { const originalFunction = targetObject[propertyName] function reactorFn(...args) { this.before && this.before(); originalFunction.apply(context, args); this.after && this.after(); } targetObject[propertyName] = reactorFn; reactorFn.before = (fn) => { this.before = fn; return reactorFn }; reactorFn.after = (fn) => { this.after = fn; return reactorFn; }; return reactorFn; } } overrideProperty(window, 'alert', window).before(() => { console.log('before') }).after(() => { console.log('after') }) alert('test')
這樣子就可以通過鏈?zhǔn)秸{(diào)用的方式來定義before
和after
的回調(diào)函數(shù)了,但是這只適用于同步執(zhí)行的方法,對于fetch
這種異步的返回Promise
的方法,為了在Promise.then
中執(zhí)行after
,又得專門寫一個重寫函數(shù),大家就根據(jù)自己的項目情況來選擇不同就寫法吧。
監(jiān)聽HTTP請求
瀏覽器中主要的HTTP請求通過XMLHttpRequest
和fetch
發(fā)出,在上面我們已經(jīng)監(jiān)聽了fetch
,接下來我們監(jiān)聽一下XMLHttpRequest
。 一般使用XMLHttpRequest
發(fā)送HTTP
請求會調(diào)用open
方法,最后調(diào)用send
方法,所以我們監(jiān)聽開始時的open
和最后的send
所以我們重寫這兩個方法
// 重寫open overrideProperty(XMLHttpRequest.prototype, 'open', (originalOpen) => { return function (...args) { // do something originalOpen.apply(this, args) } }) // 重寫send overrideProperty(XMLHttpRequest.prototype, 'send', (originalSend) => { return function (...args) { // do something originalSend.apply(this, args) } })
當(dāng)send
后,xhr對象上的readyState會經(jīng)歷四個狀態(tài),分別是: 0 (UNSENT): XMLHttpRequest 對象已經(jīng)創(chuàng)建,但 open() 方法還沒有被調(diào)用。 1 (OPENED): open() 方法已經(jīng)被調(diào)用。在這個狀態(tài)下,你可以通過設(shè)置請求頭和請求方法來配置請求。 2 (HEADERS_RECEIVED): send() 方法已經(jīng)被調(diào)用,并且頭部和狀態(tài)已經(jīng)可獲得。 3 (LOADING): 下載中;responseText 屬性已經(jīng)包含部分?jǐn)?shù)據(jù)。 4 (DONE): 請求操作已經(jīng)完成。
我們直接監(jiān)聽readyState為4(DONE)的完成狀態(tài)即可
// 重寫send overrideProperty(XMLHttpRequest.prototype, 'send', (originalSend) => { return function (...args) { // do something originalSend.apply(this, args) // 監(jiān)聽 readystatechange 事件 this.addEventListener("readystatechange", function () { // 檢查 readyState 的狀態(tài) if (this.readyState === XMLHttpRequest.DONE) { // 請求已完成,檢查狀態(tài)碼 if (this.status === 200) { // 請求成功,處理響應(yīng)數(shù)據(jù) console.log("請求成功:", this.responseText); } else { // 請求失敗,處理錯誤 console.log("請求失敗:", this.status); } } }); } })
這樣當(dāng)我們當(dāng)前頁面有fetch
請求和xhr
請求時,都可以被捕獲到。
總結(jié)
本文從Spring
出發(fā),介紹了AOP
面向切面編程的由來,又用JavaScript
演示了AOP
編程的優(yōu)勢,最后使用AOP
編程實現(xiàn)了HTTP
請求的監(jiān)聽,這大家的平時的開發(fā)中也可以靈活運用。
到此這篇關(guān)于JavaScript使用AOP編程思想實現(xiàn)監(jiān)聽HTTP請求的文章就介紹到這了,更多相關(guān)JavaScript監(jiān)聽HTTP請求內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一定有你會用到的JavaScript一行代碼實用技巧總結(jié)
這篇文章主要為大家介紹了一定有你會用到的JavaScript一行代碼總結(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07用JavaScript實現(xiàn)類似于ListBox功能示例代碼
這篇文章主要介紹了用JavaScript實現(xiàn)類似于ListBox功能,需要的朋友可以參考下2014-03-03