JavaScript使用AOP编程思想实现监听HTTP请求
AOP切面编程的概念
AOP这个概念来源于Java
的Spring
框架,是Spring
为了解决OOP(面向对象编程模式)面对一些业务场景的限制而开发来的,下面就让我用JavaScript
代替Java
来解释一下AOP
比如我现在使用OOP
写法新建一个读取数据的业务组件,分别有读取data
,更新data
,删除data
三个方法:
class DataService { constructor(ctx) { this.ctx = ctx; } createData() { ctx.createData(); } updateData() { ctx.updateData(); } deleteData() { ctx.deleteData(); } }
对于每个接口,业务可能会有一些相同的操作,如日志记录、数据检验、安全验证等,那么代码就会像下面这样
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
中都要重复执行父类的方法。
那么这时就有了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能够扫描并创建切面实例 public class LoggingAspect { // 在UserService的每个public方法前执行logBefore @Before("execution(public * com.example.UserService.*(..))") public void logBefore(JoinPoint joinPoint) { System.out.println("Logging before the method executes"); } // 在UserService的每个public方法前执行logAfter @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 以原有的函数作为参数,执行并重写原有函数 */ 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发起前做些什么 return originalFetch.apply(this, args).then((res) => { // 在fetch完成后做些什么 return res }) } })
可以看到我们第三个参数传入一个函数并返回一个函数,这个返回的函数就是重写完成的fetch
方法,只要在项目初始化时调用overrideProperty
,那么以后调用fetch
时都会执行。 是不是感觉这样写也挺麻烦的,我们换一种写法:
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
的回调函数了,但是这只适用于同步执行的方法,对于fetch
这种异步的返回Promise
的方法,为了在Promise.then
中执行after
,又得专门写一个重写函数,大家就根据自己的项目情况来选择不同就写法吧。
监听HTTP请求
浏览器中主要的HTTP请求通过XMLHttpRequest
和fetch
发出,在上面我们已经监听了fetch
,接下来我们监听一下XMLHttpRequest
。 一般使用XMLHttpRequest
发送HTTP
请求会调用open
方法,最后调用send
方法,所以我们监听开始时的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会经历四个状态,分别是: 0 (UNSENT): XMLHttpRequest 对象已经创建,但 open() 方法还没有被调用。 1 (OPENED): open() 方法已经被调用。在这个状态下,你可以通过设置请求头和请求方法来配置请求。 2 (HEADERS_RECEIVED): send() 方法已经被调用,并且头部和状态已经可获得。 3 (LOADING): 下载中;responseText 属性已经包含部分数据。 4 (DONE): 请求操作已经完成。
我们直接监听readyState为4(DONE)的完成状态即可
// 重写send overrideProperty(XMLHttpRequest.prototype, 'send', (originalSend) => { return function (...args) { // do something originalSend.apply(this, args) // 监听 readystatechange 事件 this.addEventListener("readystatechange", function () { // 检查 readyState 的状态 if (this.readyState === XMLHttpRequest.DONE) { // 请求已完成,检查状态码 if (this.status === 200) { // 请求成功,处理响应数据 console.log("请求成功:", this.responseText); } else { // 请求失败,处理错误 console.log("请求失败:", this.status); } } }); } })
这样当我们当前页面有fetch
请求和xhr
请求时,都可以被捕获到。
总结
本文从Spring
出发,介绍了AOP
面向切面编程的由来,又用JavaScript
演示了AOP
编程的优势,最后使用AOP
编程实现了HTTP
请求的监听,这大家的平时的开发中也可以灵活运用。
到此这篇关于JavaScript使用AOP编程思想实现监听HTTP请求的文章就介绍到这了,更多相关JavaScript监听HTTP请求内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
document.selection.createRange方法与实例
document.selection.createRange() 根据当前文字选择返回 TextRange 对象,或根据控件选择返回 ControlRange 对象2006-10-10兼容ie ff div 层 打开+关闭+ 拖动+遮罩+移动+动画改变高宽
div层 打开+关闭+ 拖动+遮罩+移动+动画改变高宽的实例js代码2008-07-07
最新评论