Ajax-hook原理解析

自上一篇《js 拦截全局 ajax 请求》发布以后,不少人对实现原理很是感兴趣,好,今天咱们讲内涵!javascript

若是你还不知道ajax-hook,请先了解一下:
github : github.com/wendux/Ajax…
中文介绍:www.jianshu.com/p/9b634f1c9…java

咱们首先在github上拉取源码。纳尼,这么屌炸天的功能,源码算上注释和换行总共才67行!🤔,下面咱们来一步步揭开其神秘面纱。git

总体思路-代理模式

Ajax-hook实现的总体思路是实现一个XMLHttpRequest的代理对象,而后覆盖全局的XMLHttpRequest,这样一但上层调用 new XMLHttpRequest这样的代码时,其实建立的是Ajax-hook的代理对象实例。具体原理图以下:github

ajax-hook原理图

上图中青色部分为Ajax-hook实现的代理XMLHttpRequest,内部会调用真正的XMLHttpRequest。咱们看一下hookAjax的部分源码:ajax

ob.hookAjax = function (funs) {
  //保存真正的XMLHttpRequest对象
  window._ahrealxhr = window._ahrealxhr || XMLHttpRequest
  //1.覆盖全局XMLHttpRequest,代理对象
  XMLHttpRequest = function () {
    //建立真正的XMLHttpRequest实例
    this.xhr = new window._ahrealxhr;
    for (var attr in this.xhr) {
      var type = "";
      try {
        type = typeof this.xhr[attr]
      } catch (e) {}
      if (type === "function") {
        //2.代理方法
        this[attr] = hookfun(attr);
      } else {
        //3.代理属性
        Object.defineProperty(this, attr, {
          get: getFactory(attr),
          set: setFactory(attr)
        })
      }
    }
  }
  ......复制代码

Ajax-hook 一开始先保存了真正的XMLHttpRequest对象到一个全局对象,而后在注释1处,Ajax-hook覆盖了全局的XMLHttpRequest对象,这就是代理对象的具体实现。在代理对象内部,首先建立真正的XMLHttpRequest实例,记为xhr,而后遍历xhr全部属性和方法,在2处hookfun为xhr的每个方法生成一个代理方法,在3处,经过defineProperty为每个属性生成一个代理属性。下面咱们重点看一看代理方法和代理属性的实现。json

代理方法

代理方法经过hookfun函数生成,咱们看看hookfun的具体实现:数组

function hookfun(fun) {
 return function () {
     var args = [].slice.call(arguments)
     //1.若是fun拦截函数存在,则先调用拦截函数
    if (funs[fun] && funs[fun].call(this, args, this.xhr)) {
        return;
    }
   //2.调用真正的xhr方法
   this.xhr[fun].apply(this.xhr, args);
 }
}复制代码

为了叙述清晰,咱们假设fun为 send函数,其中funs为用户提供的拦截函数对象。代码很简单,首先会根据用户提供的funs判断用户是否要拦截send, 若是提供了send的拦截方法,记为send_hook, 则上层调用代理对象send方法时,则会先调用send_hook,同时将调用参数和当前的xhr对象传递给send_hook,若是send_hook返回了true, 则调用终止,直接返回,至关于调用被终止了,若是没有返回或返回的是false,则会走到注释2处,此处调用了xhr的send方法,至此ajax send被调用成功。 因此,咱们在send_hook中能够拿到调用的参数并修改,由于参数是以数组形式传递,改变会被记录,固然,咱们也能够返回true直接终止调用。app

代理属性

属性如onload、onreadystatechange等,上层在调用ajax时一般要设置这些回调以处理请求到的数据,Ajax-hook也可以实如今请求返回时先拿到数据第一个进行处理,而后将处理过的数据传递给用户提供的回调。要实现这个功能,直接的思路就是用户设置回调时将用户提供的回调保存起来,而后设置成代理回调,当数据返回时,代理回调会被调用,而后在代理回调中首先将返回的数据提供给拦截函数处理,而后再将处理后的数据传递给用户真正的回调。那么问题来了,如何捕获用户设置回调的动做?一段典型的用户调用代码以下:函数

var xh=new XMLHttpRequest;
xh.open("https://xxx")
xh.onload=function(data){ //1
  //处理请求到的数据
}复制代码

也就是说上面代码1处的赋值时机代理对象怎么捕获?若是在赋值的时候有机会执行代码就行了。咱们回过头来看看上面原理图,有没有注意到proxy props后面的小括号里的 es5,答案就在这里! es5中对于属性引入了setter、getter,详细内容请参考:
Javascript getter: developer.mozilla.org/en-US/docs/…
Javascript setter: developer.mozilla.org/en-US/docs/…源码分析

Ajax-hook经过getFactory和setFactory生成setter、getter方法。咱们来看看它们的实现:

function getFactory(attr) {
    return function () {
        return this[attr + "_"] || this.xhr[attr]
    }
}

function setFactory(attr) {
    return function (f) {
        var xhr = this.xhr;
        var that = this;
        //区分是否回调属性
        if (attr.indexOf("on") != 0) {
            this[attr + "_"] = f;
            return;
        }
        if (funs[attr]) {
            xhr[attr] = function () {
                funs[attr](that) || f.apply(xhr, arguments);
            }
        } else {
            xhr[attr] = f;
        }
    }
}复制代码

代码比较简单,值得注意的是里面的属性加下划线是什么意思?请继续往下看。

属性修改

若是须要对返回的数据进行加工处理,好比返回的数据是json字符串,若是你想将它转化为对象再传递给上层,你会在onload回调中这么写:

xhr.responseText = JSON.parse(xhr.responseText)复制代码

可是,这里有坑,由于xhr的responseText属性并非writeable的(详情请移步 developer.mozilla.org/en-US/docs/… ),这也就意味着你没法直接更改xhr.responseText的值,而Ajax-hook也代理了这些原始属性,内部生成了一下原始属性名+下滑线的代理属性。

至此,Ajax-hook源码分析完毕。下面咱们总结一下:

Ajax-hook使用代理的方式对原生XMLHttpRequest的方法及属性进行代理,而后覆盖全局XMLHttpRequest,实现拦截全部Ajax-hook的功能。从代码角度来看,逻辑清晰,思惟巧妙,简洁优雅,值得学习。

最后

若是你喜欢,就去 github star一下吧,地址 github.com/wendux/Ajax…

本文章容许免费转载,但请注明原做者及原文连接。

相关文章
相关标签/搜索