JavaScript使用AOP編程思想實(shí)現(xiàn)監(jiān)聽HTTP請(qǐng)求
AOP切面編程的概念
AOP這個(gè)概念來源于Java
的Spring
框架,是Spring
為了解決OOP(面向?qū)ο缶幊棠J剑┟鎸?duì)一些業(yè)務(wù)場(chǎng)景的限制而開發(fā)來的,下面就讓我用JavaScript
代替Java
來解釋一下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
中可以通過注解的形式,讓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
沒有在底層實(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í)行。 是不是感覺這樣寫也挺麻煩的,我們換一種寫法:
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í)行的方法,對(duì)于fetch
這種異步的返回Promise
的方法,為了在Promise.then
中執(zhí)行after
,又得專門寫一個(gè)重寫函數(shù),大家就根據(jù)自己的項(xiàng)目情況來選擇不同就寫法吧。
監(jiān)聽HTTP請(qǐng)求
瀏覽器中主要的HTTP請(qǐng)求通過XMLHttpRequest
和fetch
發(fā)出,在上面我們已經(jīng)監(jiān)聽了fetch
,接下來我們監(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() 方法還沒有被調(diào)用。 1 (OPENED): open() 方法已經(jīng)被調(diào)用。在這個(gè)狀態(tài)下,你可以通過設(shè)置請(qǐng)求頭和請(qǐng)求方法來配置請(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
面向切面編程的由來,又用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)文章
詳解如何在webpack中做預(yù)渲染降低首屏空白時(shí)間
這篇文章主要介紹了詳解如何在webpack中做預(yù)渲染降低首屏空白時(shí)間,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08一定有你會(huì)用到的JavaScript一行代碼實(shí)用技巧總結(jié)
這篇文章主要為大家介紹了一定有你會(huì)用到的JavaScript一行代碼總結(jié),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07用javascript判斷IE版本號(hào)簡(jiǎn)單實(shí)用且向后兼容
項(xiàng)目中需要判斷IE版本號(hào),又因?yàn)?jQuery 2.0 去除了對(duì)瀏覽器版本號(hào)的判斷于是就看到一老外寫的一段代碼,下面與大家分享下2013-09-09用JavaScript實(shí)現(xiàn)類似于ListBox功能示例代碼
這篇文章主要介紹了用JavaScript實(shí)現(xiàn)類似于ListBox功能,需要的朋友可以參考下2014-03-03JavaScript日期處理類庫(kù)moment()獲取時(shí)間
moment.js是一個(gè)廣泛使用的JavaScript日期處理庫(kù),便于開發(fā)者進(jìn)行日期的解析、驗(yàn)證、操作和格式化,通過引用并設(shè)置區(qū)域,可以輕松實(shí)現(xiàn)本地化日期和時(shí)間的處理,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09