欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript使用AOP編程思想實(shí)現(xiàn)監(jiān)聽HTTP請(qǐng)求

 更新時(shí)間:2024年02月28日 15:50:56   作者:plutoLam  
這篇文章主要為大家詳細(xì)介紹了如何在JavaScript使用AOP編程思想實(shí)現(xiàn)監(jiān)聽HTTP請(qǐng)求,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

AOP切面編程的概念

AOP這個(gè)概念來源于JavaSpring框架,是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)用的方式來定義beforeafter的回調(diào)函數(shù)了,但是這只適用于同步執(zhí)行的方法,對(duì)于fetch這種異步的返回Promise的方法,為了在Promise.then中執(zhí)行after,又得專門寫一個(gè)重寫函數(shù),大家就根據(jù)自己的項(xiàng)目情況來選擇不同就寫法吧。

監(jiān)聽HTTP請(qǐng)求

瀏覽器中主要的HTTP請(qǐng)求通過XMLHttpRequestfetch發(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)文章

最新評(píng)論