阅读 766

使用Object.defineProperty实现本地Mock

前言

关于mock数据,网上已经有很多成熟的方案,但是我个人在开发的时候,我习惯性,凡事从简,我拿到接口文档的时候,我首先会选择使用这个本地mock方法,好处就是可控,适合在开发初期的时候,当然还有个好处就是如果到联调阶段的时候,也不用在改变接口地址,只要把Mock数据删掉,就能访问到真实接口。

需求

  1. 可以本地Mock数据
  2. 不用再次更改Url地址,直接使用跟后端约定好的url地址

主要流程图为以下:

算了别看了,反正现在看也看不懂啥意思。直接上代码

Mock数据存放方式

我们希望他是作为一个对象,单独放在一个js文件里面,然后我们的mock方法可以从里面提取数据 ,我们就创建一个js文件名为:test_data.js

里面有一个名为query:user的接口,这个接口里面有个mock数据,我们希望当我们请求xxx://query:user接口的时候,能够拉取到这个mock数据,当需要联调真实接口的时候,我们只需把这几行假数据去掉,便可获取到真实接口传过来的真实信息

const data = {
  "query:user": {
    "code": "S_OK",
      "record": [
        {
          "id": "132",
          "name": "silan",
          "job": "前端开发工程师",
          "workTime": "3年"
        }
      ]
  },
}
复制代码

开发MockRequest构造函数

按照惯例,我们需要一个构造函数,它有一个参数,用来接收mock数据传进去的接口地址,这个参数应该是个数组

因为你可能要mock很多接口地址的数据,因此为了让别人觉得你是个js老手,同时也为了代码茁壮性,我们需要声明一个类class,

class MockRequest{
      constructor(rules) {
        this.rules = rules // 获取假数据的接口地址
    }
}

复制代码

接下来就是比较复杂的部分了,我们需要定义一个原型方法,在它内部需要完成三件事:

  1. 用一个对象做XMLHttpRequest的替代对象,改写open和send方法,用它来代替发送请求
  2. 匹配url地址,如果请求的url地址为有mock数据的地址则获取mock数据
  3. 匹配url地址,如果不是有mock数据的地址,则发出http请求获取真实接口数据

暂且将其命名为create

class MockRequest{
      constructor(rules) {
        this.rules = rules // 获取假数据的接口地址
    }
    create(){
    // 匹配Url方法
        function matchRules(url) {
            var r = self.rules;
            var result = [];
            for (var i = 0; i < r.length; i++) {
                if (r[i] && url.indexOf(r[i].url) >= 0) {
                    result.push(r[i]);
                }
            }
            if (result.length > 0) {
                return result[0]
            }
        }
    }
}

复制代码

接着补上比较复杂的替代对象

class MockRequest {
    constructor(rules) {
        this.rules = rules
    }
    create() {
        let self = this;
        // 匹配url
        function matchRules(url) {
            //...
        }
        // returnObj 拥有open 和send方法
        let returnObj = {
            // onreadystatechange: null,
            open: function (method, url, async) {
                this.url = url;
                this.xhr.open(method, url, async);
            },
            send: function (data) {
                // this指向MockRequestContructor
                let self = this;
                //获取匹配到的url地址
                let rule = matchRules(self.url);
                if (rule) {
                // 如果匹配地址为mock的地址则把mock数据传给前端
                    setTimeout(function () {
                        self.readyState = 4;
                        self.status = 200;
                        //获取返回的mock数据的responseText
                        if (typeof rule.responseText == "string") {
                            self.responseText = rule.responseText;
                        }  else if (typeof rule.responseText == "object") {
                            self.responseText = JSON.stringify(rule.responseText);
                        }
                        //触发用户在前端设置的onreadyStatechange方法,前端就能获取到mock数据
                        self.onreadystatechange && self.onreadystatechange();
                    }, 100);

                } else {
                // 如果不是mock地址,则发生原生请求
                    self.xhr.send(data);
                }
            }
        }
        // 讲XMLHttpRequest构造函数赋值给_XMLHttpRequest
        let _XMLHttpRequest = window.XMLHttpRequest
        function MockRequestContructor() {
            this.xhr = new _XMLHttpRequest(); 
            // 把XMLHttpRequest实例赋值到this.xhr,为的是方便Object.defineProperty操作
        }
        // 此时的MockRequestContr是个构造函数,把returnObj里面的属性和方法绑定到他的原型上
        MockRequestContructor.prototype = returnObj;
        //这时候前端调用的 new XMLHttpRequest
        //已经被我们成功改写,调用的其实是MockRequestContructor这个构造函数
        window.XMLHttpRequest = MockRequestContructor;
    }
}

复制代码

走到这一步,其实已经完成了获取mock数据的操作,当我们拉取一个已经设置好mock数据的url地址的时候,返回的是mock数据,而不会发送任何请求都目标服务器

剩下来的就只有实现

“当不是mock数据地址的时候,发送请求到目标服务器获取真实数据” 功能

我们都知道,获取responseText只能在onreadystatechange中获取,而目前原来的XMLHttpRequest只能获取到mock数据,因此我们要通过Object.defineProperty对其进行改写,当前端在设置 xhr.onreadystatechange=function(){xxxx}的时候就触发Object.defineProperty,我们就可以把获取到的真实数据手动传入到onreadystatechange里面

        Object.defineProperty(returnObj, "onreadystatechange", {
            get: function () {
                return this.xhr.onreadystatechange;
            },
            set: function (func) {
                var obj = this; // 这里的obj指的是 MockRequestContructor
                console.log(obj,func)
                // 如果obj.xhr.onreadystatechange有变化,说明是真实请求地址
                obj.xhr.onreadystatechange = function (arg) {
                    // this指向http请求真实数据
                    // returnObj就是获取到的返回对象,把真实的值替换回去
                    returnObj.readyState =this.readyState;
                    returnObj.status =this.status;
                    returnObj.responseText =this.responseText;
                    returnObj.responseXML =this.responseXML;
                    func(arg);
                };
            }
        })
复制代码

完整代码


class MockRequest {
    constructor(rules) {
        this.rules = rules
    }
    create() {
        let self = this;
        // 匹配url
        function matchRules(url) {
            let r = self.rules;
            let result = [];
            for (let i = 0; i < r.length; i++) {
                if (r[i] && url.indexOf(r[i].url) >= 0) {
                    result.push(r[i]);
                }
            }
            if (result.length > 0) {
                return result[0]
            }
        }
        // returnObj 拥有open 和send方法
        let returnObj = {
            // onreadystatechange: null,
            open: function (method, url, async) {
                this.url = url;
                this.xhr.open(method, url, async);
            },
            send: function (data) {
                // this指向MockRequestContructor
                let self = this;
                //获取匹配到的url地址
                let rule = matchRules(self.url);
                if (rule) {
                    // 如果匹配地址为mock的地址则把mock数据传给前端
                    setTimeout(function () {
                        self.readyState = 4;
                        self.status = 200;
                        //获取返回的mock数据的responseText
                        if (typeof rule.responseText == "string") {
                            self.responseText = rule.responseText;
                        } else if (typeof rule.responseText == "object") {
                            self.responseText = JSON.stringify(rule.responseText);
                        }
                        //触发用户在前端设置的onreadyStatechange方法,前端就能获取到mock数据
                        self.onreadystatechange && self.onreadystatechange();
                    }, 100);

                } else {
                    // 如果不是mock地址,则发生原生请求
                    self.xhr.send(data);
                }
            }
        }
        // 监听onreadystatechange变化
        Object.defineProperty(returnObj, "onreadystatechange", {
            get: function () {
                return this.xhr.onreadystatechange;
            },
            set: function (func) {
                var obj = this; // 这里的obj指的是 MockRequestContructor
                // 如果obj.xhr.onreadystatechange有变化,说明是真实请求地址
                obj.xhr.onreadystatechange = function (arg) {
                    // this指向http请求真实数据
                    // returnObj就是获取到的返回对象,把真实的值替换回去
                    returnObj.readyState = this.readyState;
                    returnObj.status = this.status;
                    returnObj.responseText = this.responseText;
                    returnObj.responseXML = this.responseXML;
                    func(arg);
                };
            }
        })
        // 讲XMLHttpRequest构造函数赋值给_XMLHttpRequest
        let _XMLHttpRequest = window.XMLHttpRequest
        function MockRequestContructor() {
            this.xhr = new _XMLHttpRequest();
            // 把XMLHttpRequest实例赋值到this.xhr,为的是方便Object.defineProperty操作
        }
        // 此时的MockRequestContr是个构造函数,把returnObj里面的属性和方法绑定到他的原型上
        MockRequestContructor.prototype = returnObj;
        //这时候前端调用的 new XMLHttpRequest
        //已经被我们成功改写,调用的其实是MockRequestContructor这个构造函数
        window.XMLHttpRequest = MockRequestContructor;
    }
}

复制代码

在项目中使用

只需要两步

  1. 配置好Mock数据
  2. 引入mock数据的js文件和mock方法

1.配置mock

在放置mock数据的test_data.js中配置好mock数据,然后new MockRequest

const data = {
  "query:user": {
    "code": "S_OK",
      "record": [
        {
          "id": "132",
          "name": "silan",
          "job": "前端开发工程师",
          "workTime": "3年"
        }
      ]
  },
}

var rules = [];
for (var key in data) {
//转为数组
  rules.push({
    url: key,
    responseText: data[key]
  })
}
// 使用mock
 new MockRequest(rules).create()

复制代码

2.引入mock数据js和mock方法js

<html lang="en">
<head>
  <meta charset="UTF-8">
  <!--引入文件-->
  <!--mock方法-->
  <script src="./mockrequest.js"></script>
  <!--mock数据-->
  <script src="./test_data.js"></script>
  <title>mock</title>
</head>
<body>

  <script>
    var xhr = new XMLHttpRequest();
    // xhr.open('GET', 'https://www.apiopen.top/journalismApi') //这里是真实接口
    xhr.open('GET', 'query:user') // mock数据接口
    xhr.send()

    xhr.onreadystatechange = function (res) {
      if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(JSON.parse(xhr.responseText))
      }
    }
  </script>
</body>
</html>
复制代码

可以成功获取测试数据

然后我们再试下真实数据接口,把// xhr.open('GET', 'https://www.apiopen.top/journalismApi')这行取消注释,并且注释掉mock数据请求方法

也能够成功获取

vue获取其他框架项目中使用方法也是一样的,引入两个js文件就行了,当到联调阶段的时候,就把联调的那个接口地址注释掉就行了,便能获取到真实的数据

写在最后

本文只是提供给各位一个Object.defineProperty的扩展使用思路,正如掘金console所言

[不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶]

谢谢各位

关注下面的标签,发现更多相似文章
评论