前言
关于mock数据,网上已经有很多成熟的方案,但是我个人在开发的时候,我习惯性,凡事从简,我拿到接口文档的时候,我首先会选择使用这个本地mock方法,好处就是可控,适合在开发初期的时候,当然还有个好处就是如果到联调阶段的时候,也不用在改变接口地址,只要把Mock数据删掉,就能访问到真实接口。
需求
- 可以本地Mock数据
- 不用再次更改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 // 获取假数据的接口地址
}
}
接下来就是比较复杂的部分了,我们需要定义一个原型方法,在它内部需要完成三件事:
- 用一个对象做XMLHttpRequest的替代对象,改写open和send方法,用它来代替发送请求
- 匹配url地址,如果请求的url地址为有mock数据的地址则获取mock数据
- 匹配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;
}
}
在项目中使用
只需要两步
- 配置好Mock数据
- 引入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所言
[不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶]
谢谢各位