终于可来搞一搞日志模块的源码了,其实代码都很简单(哈哈哈),我开了一下git的日志,想来我们现在看到的代码,都是之前迭代的代码,不是一开始,一个函数就很多行代码的,所以要理清条路,理解为什么这样加代码,当然能一开始就分清楚函数定义多少个,后面好加代码,扩展的 这个思路架构也是我们要瞻仰,学习的。
log这个模块的类图结构知道,它在VConsolePlugin上又封装了一层,类图结构件上一篇文章上一篇文章
即VConsoleLogTab 作为一个log模块基础的类结构
废话不多说直接贴代码看实现,代码才是王道
什么构造函数和初始化就不说了,直接来看怎么替代js浏览器对象的打印
/**
* replace window.console with vConsole method
* @private
*/
mockConsole() {
const that = this;
const methodList = ['log', 'info', 'warn', 'debug', 'error'];
if (!window.console) {
window.console = {};
} else {
methodList.map(function(method) {
that.console[method] = window.console[method];
});
that.console.time = window.console.time;
that.console.timeEnd = window.console.timeEnd;
that.console.clear = window.console.clear;
}
methodList.map(method => {
window.console[method] = (...args) => {
this.printLog({
logType: method,
logs: args
});
};
});
const timeLog = {}
window.console.time = function(label) {
timeLog[label] = Date.now();
};
window.console.timeEnd = function(label) {
var pre = timeLog[label];
if (pre) {
console.log(label + ':', (Date.now() - pre) + 'ms');
delete timeLog[label];
} else {
console.log(label + ': 0ms');
}
};
window.console.clear = (...args) => {
that.clearLog();
that.console.clear.apply(window.console, args);
};
}
该方法就是模拟console对象
- 判断window的console对象是否存在
- 如果不存在就让console = {},for循环自己实现,console的erron,log等方法,
- 如果存在,劫持console.log的各个方法,将方法保存在that对象中,其实我也不知道这个是个什么意思,我觉得这个劫持可以不要,反正你后面都是全部自己实现了log,error,warn,clear,time,timeEnd,等方法,不知道作者这里是个什么意思
这样差不多log模块毕,接下来看看network,这里就不卖关子了,其实网络请求也是通过类似于日志模块模拟ajax的方式。
在此之前,来梳理下window.XMLHttpRequest对象的网络请求流程
- 在后台与服务器交换数据,就会用到XMLHttpRequest对象
- 网络请求发生时,先回创建XMLHttpRequest对象即readyState = 0 也是调用open()方法
- 紧接着XMLHttpRequest对象调用send()方法,发送客户端网络请求,等待服务器返回,即readyState的值为1 ,2,3的变化中(其中何时变化为何值可以参考下https://www.cnblogs.com/liujiale/p/5388110.html)
- 再次XMLHttpRequest等待服务器响应完毕,readyState为4
注意这个onreadystatechange这个事件很重要,每当readyState值变化是都会触发这个方法,在这个方法我们就可以取用XMLHttpRequest对象的一些有用的属性值来做文章了比如responseText,status等
所以了解了这个过程,在来看下面的代码就很简单,说白了就是重写了send,open,onreadystatechange等方法
/**
* mock ajax request
* @private
*/
mockAjax() {
let _XMLHttpRequest = window.XMLHttpRequest;
if (!_XMLHttpRequest) { return; }
//将this存储起来
let that = this;
let _open = window.XMLHttpRequest.prototype.open,
_send = window.XMLHttpRequest.prototype.send;
that._open = _open;
that._send = _send;
// 模拟XMLHttpRequest的open方法
window.XMLHttpRequest.prototype.open = function() {
let XMLReq = this;
//分割请求的参数
let args = [].slice.call(arguments),
method = args[0],
url = args[1],
id = that.getUniqueID(); //设置一个值存储当前请求的唯一id,唯一标识
//定义一个时间计时器
let timer = null;
// may be used by other functions
XMLReq._requestID = id;
XMLReq._method = method;
XMLReq._url = url;
// 模拟XMLHttpRequest的onreadystatechange
let _onreadystatechange = XMLReq.onreadystatechange || function() {};
let onreadystatechange = function() {
let item = that.reqList[id] || {};
// update status
item.readyState = XMLReq.readyState;
item.status = 0;
if (XMLReq.readyState > 1) {
item.status = XMLReq.status;
}
item.responseType = XMLReq.responseType;
if (XMLReq.readyState == 0) {
// UNSENT
if (!item.startTime) {
item.startTime = (+new Date());
}
} else if (XMLReq.readyState == 1) {
// OPENED
if (!item.startTime) {
item.startTime = (+new Date());
}
} else if (XMLReq.readyState == 2) {
// HEADERS_RECEIVED
item.header = {};
let header = XMLReq.getAllResponseHeaders() || '',
headerArr = header.split("\n");
// extract plain text to key-value format
for (let i = 0; i < headerArr.length; i++) {
let line = headerArr[i];
if (!line) { continue; }
let arr = line.split(': ');
let key = arr[0],
value = arr.slice(1).join(': ');
item.header[key] = value;
}
} else if (XMLReq.readyState == 3) {
// LOADING
} else if (XMLReq.readyState == 4) {
// DONE
clearInterval(timer);
item.endTime = +new Date(),
item.costTime = item.endTime - (item.startTime || item.endTime);
item.response = XMLReq.response;
} else {
clearInterval(timer);
}
if (!XMLReq._noVConsole) {
that.updateRequest(id, item);
}
return _onreadystatechange.apply(XMLReq, arguments);
};
//覆盖原始默认的onreadystatechange
XMLReq.onreadystatechange = onreadystatechange;
//为了怕请求过程占用第三方应用汇修改xhr默认的方法,所以用了一个定时器循环来监听readyState的变化
let preState = -1;
timer = setInterval(function() {
if (preState != XMLReq.readyState) {
preState = XMLReq.readyState;
onreadystatechange.call(XMLReq);
}
}, 10);
return _open.apply(XMLReq, args);
};
// 默认send方法
window.XMLHttpRequest.prototype.send = function() {
let XMLReq = this;
let args = [].slice.call(arguments),
data = args[0];
//重请求池找出相应的请求
let item = that.reqList[XMLReq._requestID] || {};
item.method = XMLReq._method.toUpperCase();
//处理url后面跟着的参数,
//1,先以?分割为数组
let query = XMLReq._url.split('?'); // a.php?b=c&d=?e => ['a.php', 'b=c&d=', '?e']
// 2,在去除最前面的数组
item.url = query.shift(); // => ['b=c&d=', '?e']
if (query.length > 0) {
item.getData = {};
//3,然后剩下的?又重新连接在一起
query = query.join('?'); // => 'b=c&d=?e'
//4,在以& 去键值对
query = query.split('&'); // => ['b=c', 'd=?e']
for (let q of query) {
q = q.split('=');
item.getData[q[0]] = q[1];
}
}
//处理post请求方式,注意这里 会有url接参数,但又是post请求的情况,这里也能处理
if (item.method == 'POST') {
// save POST data
if (tool.isString(data)) {
let arr = data.split('&');
item.postData = {};
for (let q of arr) {
q = q.split('=');
item.postData[q[0]] = q[1];
}
} else if (tool.isPlainObject(data)) {
item.postData = data;
}
}
if (!XMLReq._noVConsole) {
that.updateRequest(XMLReq._requestID, item);
}
return _send.apply(XMLReq, args);
};
};
这个就比较底层了,其实也不是,只不过我们平时开发的时候,习惯用封装好的比如jq等,其实背后做了很多时候事情!
接下来是最后的存储模块,VConsole里主要是列了cookie和localStorage的存储
localStorage就不说了,贴心cookie的的代码
getCookieList() {
if (!document.cookie || !navigator.cookieEnabled) {
return [];
}
let list = [];
let items = document.cookie.split(';');
for (let i=0; i<items.length; i++) {
let item = items[i].split('=');
let name = item.shift().replace(/^ /, ''),
value = item.join('=');
list.push({
name: decodeURIComponent(name),
value: decodeURIComponent(value)
});
}
return list;
}
结束了,以上是个人见解,欢迎指正批评!谢谢