JavaScript使用AOP編程思想實(shí)現(xiàn)監(jiān)聽HTTP請(qǐng)求
AOP切面編程的概念
AOP這個(gè)概念來(lái)源于Java的Spring框架,是Spring為了解決OOP(面向?qū)ο缶幊棠J剑┟鎸?duì)一些業(yè)務(wù)場(chǎng)景的限制而開發(fā)來(lái)的,下面就讓我用JavaScript代替Java來(lái)解釋一下AOP
比如我現(xiàn)在使用OOP寫法新建一個(gè)讀取數(shù)據(jù)的業(yè)務(wù)組件,分別有讀取data,更新data,刪除data三個(gè)方法:
class DataService {
constructor(ctx) {
this.ctx = ctx;
}
createData() {
ctx.createData();
}
updateData() {
ctx.updateData();
}
deleteData() {
ctx.deleteData();
}
}
對(duì)于每個(gè)接口,業(yè)務(wù)可能會(huì)有一些相同的操作,如日志記錄、數(shù)據(jù)檢驗(yàn)、安全驗(yàn)證等,那么代碼就會(huì)像下面這樣
class DataService {
constructor(ctx) {
this.ctx = ctx;
}
createData() {
ctx.dataCheck();
ctx.createData();
}
updateData() {
ctx.dataCheck();
ctx.updateData();
}
deleteData() {
ctx.dataCheck();
ctx.deleteData();
}
}
一個(gè)相同的功能在很多不同的方法中以相同的方式出現(xiàn),這樣顯然不符合編碼的簡(jiǎn)潔性和易讀性。 有一種解決方法是使用Proxy模式,為了保持我們的代碼中一個(gè)類只負(fù)責(zé)一件事的原則,新建一個(gè)新的類繼承于DataService,在這個(gè)類中為每個(gè)方法都加上dataCheck
class CheckDataService extends DataService {
constructor(ctx) {
super(ctx)
}
createData() {
ctx.dataCheck();
ctx.createData();
}
updateData() {
ctx.dataCheck();
ctx.updateData();
}
deleteData() {
ctx.dataCheck();
ctx.deleteData();
}
}
這樣的做法缺點(diǎn)是比較麻煩,每個(gè)Proxy中都要重復(fù)執(zhí)行父類的方法。
那么這時(shí)就有了AOP,其基本原理是在Spring中某個(gè)類的運(yùn)行期的前期或者后期插入某些邏輯,在Spring中可以通過(guò)注解的形式,讓Spring容器啟動(dòng)時(shí)實(shí)現(xiàn)自動(dòng)注入,如下面代碼片段
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)建切面實(shí)例
public class LoggingAspect {
// 在UserService的每個(gè)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的每個(gè)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 // 聲明為一個(gè)Spring管理的組件
public class UserService {
public void login() {
System.out.println("Doing some important work");
}
}
但是JS沒(méi)有在底層實(shí)現(xiàn)這些東西,所以我們只能自己改造。
在JavaScript中實(shí)現(xiàn)AOP
思路是這樣的,我們將某個(gè)需要切入的方法進(jìn)行重寫,在重寫后的函數(shù)中切入相關(guān)邏輯就行了
/**
* 重寫對(duì)象上面的某個(gè)屬性
* @param targetObject 需要被重寫的對(duì)象
* @param propertyName 需要被重寫對(duì)象的 key
* @param newImplementation 以原有的函數(shù)作為參數(shù),執(zhí)行并重寫原有函數(shù)
*/
function overrideProperty(
targetObject,
propertyName,
newImplementation,
) {
if (targetObject === undefined) return // 若當(dāng)前對(duì)象不存在
if (propertyName in targetObject) { // 若當(dāng)前對(duì)象存在當(dāng)前屬性
const originalFunction = targetObject[propertyName]
const modifiedFunction = newImplementation(originalFunction) // 把原本的函數(shù)傳入
if (typeof modifiedFunction == 'function') {
targetObject[propertyName] = modifiedFunction
}
}
}
先寫一個(gè)公共方法,去重寫對(duì)象上的某個(gè)屬性,這樣我們可以調(diào)用overrideProperty去重寫任意對(duì)象上的任何方法。 現(xiàn)在用overrideProperty重寫一下window上的fetch方法
overrideProperty(window, 'fetch', originalFetch => {
return function (...args) {
// 在fetch發(fā)起前做些什么
return originalFetch.apply(this, args).then((res) => {
// 在fetch完成后做些什么
return res
})
}
})
可以看到我們第三個(gè)參數(shù)傳入一個(gè)函數(shù)并返回一個(gè)函數(shù),這個(gè)返回的函數(shù)就是重寫完成的fetch方法,只要在項(xiàng)目初始化時(shí)調(diào)用overrideProperty,那么以后調(diào)用fetch時(shí)都會(huì)執(zhí)行。 是不是感覺(jué)這樣寫也挺麻煩的,我們換一種寫法:
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')
這樣子就可以通過(guò)鏈?zhǔn)秸{(diào)用的方式來(lái)定義before和after的回調(diào)函數(shù)了,但是這只適用于同步執(zhí)行的方法,對(duì)于fetch這種異步的返回Promise的方法,為了在Promise.then中執(zhí)行after,又得專門寫一個(gè)重寫函數(shù),大家就根據(jù)自己的項(xiàng)目情況來(lái)選擇不同就寫法吧。
監(jiān)聽HTTP請(qǐng)求
瀏覽器中主要的HTTP請(qǐng)求通過(guò)XMLHttpRequest和fetch發(fā)出,在上面我們已經(jīng)監(jiān)聽了fetch,接下來(lái)我們監(jiān)聽一下XMLHttpRequest。 一般使用XMLHttpRequest發(fā)送HTTP請(qǐng)求會(huì)調(diào)用open方法,最后調(diào)用send方法,所以我們監(jiān)聽開始時(shí)的open和最后的send 所以我們重寫這兩個(gè)方法
// 重寫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對(duì)象上的readyState會(huì)經(jīng)歷四個(gè)狀態(tài),分別是: 0 (UNSENT): XMLHttpRequest 對(duì)象已經(jīng)創(chuàng)建,但 open() 方法還沒(méi)有被調(diào)用。 1 (OPENED): open() 方法已經(jīng)被調(diào)用。在這個(gè)狀態(tài)下,你可以通過(guò)設(shè)置請(qǐng)求頭和請(qǐng)求方法來(lái)配置請(qǐng)求。 2 (HEADERS_RECEIVED): send() 方法已經(jīng)被調(diào)用,并且頭部和狀態(tài)已經(jīng)可獲得。 3 (LOADING): 下載中;responseText 屬性已經(jīng)包含部分?jǐn)?shù)據(jù)。 4 (DONE): 請(qǐng)求操作已經(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) {
// 請(qǐng)求已完成,檢查狀態(tài)碼
if (this.status === 200) {
// 請(qǐng)求成功,處理響應(yīng)數(shù)據(jù)
console.log("請(qǐng)求成功:", this.responseText);
} else {
// 請(qǐng)求失敗,處理錯(cuò)誤
console.log("請(qǐng)求失敗:", this.status);
}
}
});
}
})
這樣當(dāng)我們當(dāng)前頁(yè)面有fetch請(qǐng)求和xhr請(qǐng)求時(shí),都可以被捕獲到。
總結(jié)
本文從Spring出發(fā),介紹了AOP面向切面編程的由來(lái),又用JavaScript演示了AOP編程的優(yōu)勢(shì),最后使用AOP編程實(shí)現(xiàn)了HTTP請(qǐng)求的監(jiān)聽,這大家的平時(shí)的開發(fā)中也可以靈活運(yùn)用。
到此這篇關(guān)于JavaScript使用AOP編程思想實(shí)現(xiàn)監(jiān)聽HTTP請(qǐng)求的文章就介紹到這了,更多相關(guān)JavaScript監(jiān)聽HTTP請(qǐng)求內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用focus方法讓光標(biāo)默認(rèn)停留在INPUT框
讓光標(biāo)默認(rèn)停留在INPUT框中,用focus方法可以實(shí)現(xiàn),下面有個(gè)示例代碼,需要的朋友可以參考下2014-07-07
JavaScript實(shí)現(xiàn)星級(jí)評(píng)價(jià)效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)星級(jí)評(píng)價(jià)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
JavaScript基于querySelector?/?querySelectorAll對(duì)元素的操作(DOM?AP
這篇文章主要介紹了JavaScript基于querySelector?/?querySelectorAll對(duì)元素的操作(DOM?API掃盲),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-12-12
JavaScript中.min.js和.js文件的區(qū)別講解
今天小編就為大家分享一篇關(guān)于JavaScript中.min.js和.js文件的區(qū)別講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
使用Webpack進(jìn)行高效分包優(yōu)化的實(shí)現(xiàn)
本文主要介紹了使用Webpack進(jìn)行高效分包優(yōu)化的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-06-06
bootstrap jquery dataTable 異步ajax刷新表格數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要介紹了bootstrap jquery dataTable 異步ajax刷新表格數(shù)據(jù)的實(shí)現(xiàn)方法,需要的朋友可以參考下2017-02-02
JavaScript中的值是按值傳遞還是按引用傳遞問(wèn)題探討
這篇文章主要介紹了JavaScript中的值是按值傳遞還是按引用傳遞問(wèn)題探討,本文講解了按值傳遞、按引用傳遞、按共享傳遞、基本類型的不可變(immutable)性質(zhì)等內(nèi)容,需要的朋友可以參考下2015-01-01
JavaScript如何實(shí)現(xiàn)跨域請(qǐng)求
這篇文章主要為大家詳細(xì)介紹了JavaScript如何實(shí)現(xiàn)跨域請(qǐng)求,告訴大家什么是跨域請(qǐng)求?什么時(shí)候會(huì)用到跨域請(qǐng)求?感興趣的小伙伴們可以參考一下2016-08-08

