javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JavaScript监听HTTP请求

JavaScript使用AOP编程思想实现监听HTTP请求

作者:plutoLam

这篇文章主要为大家详细介绍了如何在JavaScript使用AOP编程思想实现监听HTTP请求,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

AOP切面编程的概念

AOP这个概念来源于JavaSpring框架,是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')

这样子就可以通过链式调用的方式来定义beforeafter的回调函数了,但是这只适用于同步执行的方法,对于fetch这种异步的返回Promise的方法,为了在Promise.then中执行after,又得专门写一个重写函数,大家就根据自己的项目情况来选择不同就写法吧。

监听HTTP请求

浏览器中主要的HTTP请求通过XMLHttpRequestfetch发出,在上面我们已经监听了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请求内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文