JavaScript使用AOP編程思想實現監(jiān)聽HTTP請求
AOP切面編程的概念
AOP這個概念來源于Java的Spring框架,是Spring為了解決OOP(面向對象編程模式)面對一些業(yè)務場景的限制而開發(fā)來的,下面就讓我用JavaScript代替Java來解釋一下AOP
比如我現在使用OOP寫法新建一個讀取數據的業(yè)務組件,分別有讀取data,更新data,刪除data三個方法:
class DataService {
constructor(ctx) {
this.ctx = ctx;
}
createData() {
ctx.createData();
}
updateData() {
ctx.updateData();
}
deleteData() {
ctx.deleteData();
}
}
對于每個接口,業(yè)務可能會有一些相同的操作,如日志記錄、數據檢驗、安全驗證等,那么代碼就會像下面這樣
class DataService {
constructor(ctx) {
this.ctx = ctx;
}
createData() {
ctx.dataCheck();
ctx.createData();
}
updateData() {
ctx.dataCheck();
ctx.updateData();
}
deleteData() {
ctx.dataCheck();
ctx.deleteData();
}
}
一個相同的功能在很多不同的方法中以相同的方式出現,這樣顯然不符合編碼的簡潔性和易讀性。 有一種解決方法是使用Proxy模式,為了保持我們的代碼中一個類只負責一件事的原則,新建一個新的類繼承于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中都要重復執(zhí)行父類的方法。
那么這時就有了AOP,其基本原理是在Spring中某個類的運行期的前期或者后期插入某些邏輯,在Spring中可以通過注解的形式,讓Spring容器啟動時實現自動注入,如下面代碼片段
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沒有在底層實現這些東西,所以我們只能自己改造。
在JavaScript中實現AOP
思路是這樣的,我們將某個需要切入的方法進行重寫,在重寫后的函數中切入相關邏輯就行了
/**
* 重寫對象上面的某個屬性
* @param targetObject 需要被重寫的對象
* @param propertyName 需要被重寫對象的 key
* @param newImplementation 以原有的函數作為參數,執(zhí)行并重寫原有函數
*/
function overrideProperty(
targetObject,
propertyName,
newImplementation,
) {
if (targetObject === undefined) return // 若當前對象不存在
if (propertyName in targetObject) { // 若當前對象存在當前屬性
const originalFunction = targetObject[propertyName]
const modifiedFunction = newImplementation(originalFunction) // 把原本的函數傳入
if (typeof modifiedFunction == 'function') {
targetObject[propertyName] = modifiedFunction
}
}
}
先寫一個公共方法,去重寫對象上的某個屬性,這樣我們可以調用overrideProperty去重寫任意對象上的任何方法。 現在用overrideProperty重寫一下window上的fetch方法
overrideProperty(window, 'fetch', originalFetch => {
return function (...args) {
// 在fetch發(fā)起前做些什么
return originalFetch.apply(this, args).then((res) => {
// 在fetch完成后做些什么
return res
})
}
})
可以看到我們第三個參數傳入一個函數并返回一個函數,這個返回的函數就是重寫完成的fetch方法,只要在項目初始化時調用overrideProperty,那么以后調用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')
這樣子就可以通過鏈式調用的方式來定義before和after的回調函數了,但是這只適用于同步執(zhí)行的方法,對于fetch這種異步的返回Promise的方法,為了在Promise.then中執(zhí)行after,又得專門寫一個重寫函數,大家就根據自己的項目情況來選擇不同就寫法吧。
監(jiān)聽HTTP請求
瀏覽器中主要的HTTP請求通過XMLHttpRequest和fetch發(fā)出,在上面我們已經監(jiān)聽了fetch,接下來我們監(jiān)聽一下XMLHttpRequest。 一般使用XMLHttpRequest發(fā)送HTTP請求會調用open方法,最后調用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)
}
})
當send后,xhr對象上的readyState會經歷四個狀態(tài),分別是: 0 (UNSENT): XMLHttpRequest 對象已經創(chuàng)建,但 open() 方法還沒有被調用。 1 (OPENED): open() 方法已經被調用。在這個狀態(tài)下,你可以通過設置請求頭和請求方法來配置請求。 2 (HEADERS_RECEIVED): send() 方法已經被調用,并且頭部和狀態(tài)已經可獲得。 3 (LOADING): 下載中;responseText 屬性已經包含部分數據。 4 (DONE): 請求操作已經完成。
我們直接監(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) {
// 請求成功,處理響應數據
console.log("請求成功:", this.responseText);
} else {
// 請求失敗,處理錯誤
console.log("請求失敗:", this.status);
}
}
});
}
})
這樣當我們當前頁面有fetch請求和xhr請求時,都可以被捕獲到。
總結
本文從Spring出發(fā),介紹了AOP面向切面編程的由來,又用JavaScript演示了AOP編程的優(yōu)勢,最后使用AOP編程實現了HTTP請求的監(jiān)聽,這大家的平時的開發(fā)中也可以靈活運用。
到此這篇關于JavaScript使用AOP編程思想實現監(jiān)聽HTTP請求的文章就介紹到這了,更多相關JavaScript監(jiān)聽HTTP請求內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

