使用Object.defineProperty實現本地Mock

前言

關於mock數據,網上已經有不少成熟的方案,可是我我的在開發的時候,我習慣性,凡事從簡,我拿到接口文檔的時候,我首先會選擇使用這個本地mock方法,好處就是可控,適合在開發初期的時候,固然還有個好處就是若是到聯調階段的時候,也不用在改變接口地址,只要把Mock數據刪掉,就能訪問到真實接口。javascript

需求

  1. 能夠本地Mock數據
  2. 不用再次更改Url地址,直接使用跟後端約定好的url地址

主要流程圖爲如下: html

算了別看了,反正如今看也看不懂啥意思。直接上代碼

Mock數據存放方式

咱們但願他是做爲一個對象,單獨放在一個js文件裏面,而後咱們的mock方法能夠從裏面提取數據 ,咱們就建立一個js文件名爲:test_data.js前端

裏面有一個名爲query:user的接口,這個接口裏面有個mock數據,咱們但願當咱們請求xxx://query:user接口的時候,可以拉取到這個mock數據,當須要聯調真實接口的時候,咱們只需把這幾行假數據去掉,即可獲取到真實接口傳過來的真實信息vue

const data = {
  "query:user": {
    "code": "S_OK",
      "record": [
        {
          "id": "132",
          "name": "silan",
          "job": "前端開發工程師",
          "workTime": "3年"
        }
      ]
  },
}
複製代碼

開發MockRequest構造函數

按照慣例,咱們須要一個構造函數,它有一個參數,用來接收mock數據傳進去的接口地址,這個參數應該是個數組java

由於你可能要mock不少接口地址的數據,所以爲了讓別人以爲你是個js老手,同時也爲了代碼茁壯性,咱們須要聲明一個類class,後端

class MockRequest{
      constructor(rules) {
        this.rules = rules // 獲取假數據的接口地址
    }
}

複製代碼

接下來就是比較複雜的部分了,咱們須要定義一個原型方法,在它內部須要完成三件事:api

  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所言

[不要吹滅你的靈感和你的想象力; 不要成爲你的模型的奴隸]

謝謝各位

相關文章
相關標籤/搜索