记一次工具优化历程
最近公司搞了一系列的专题活动。因为不是公司的主线任务,所以后台的同学支援的也很有限。提供了一系列的ajax接口供前端同学调用,紧赶慢赶也是把工作完满结束。然后回过头来一看,发现很多重复的接口数据调用,并且数据结构也是一样的。后台的同学是把所有的数据字段都发到的前端,所有要用那些字段全凭需要。所以就想能不能进行一次数据接口请求的封装,在下次需要的时候只需要调用然后所有的事情就解决了。这样就可以减轻前端同学重复的垒代码。既然想好了就开始干。
先理理需求
- 统一的接口调用
- 数据与文档组装返回
- 有请求延时需求
其实需求很简单,对应的解决方案也没什么挑战难度的。说说我想的具体思路吧!
- 首先对于接口的统一调用,就是以参数形式构造函数的方式来触发。
- 拿到后台返回值后与dom组装进行,将数据组装为统一的dom结构(因为所有的数据都返回过来了)。对于不需要的数据用css来进行隐藏。
- 对于延时请求,首先我想到的是用setTimeout来进行延时。但最终放弃了,感觉这样不太明智(也没什么技术含量,哈哈~)。只是没有将所有请求延后,所以想是否可以到哪个块儿就请求那个块儿的数据。所以想用图片懒加载的思路来解决延时。
说了这么多还是直接上代码吧。
var utils = (function(mod) {
/**
* [ajaxAPI]
* ajax 获取问文章接口
* @author mao
* @version 1
* @date 2016-09-08
* @param {Object} option {url:请求地址,count:显示条数,elem:dom元素,flag:延时请求true || false,type:class类类型}
*/
mod.ajaxAPI = function(option) {
var url = option.url,
count = option.count || 3,
elem = option.elem,
flag = option.flag || false,
type = option.type || '';
count = parseInt(count);
//判断是否延时
if(!flag) {
getData(url, count, elem, type);
} else {
//轮动检测
$(window).scroll(function() {
//dom块儿是否进入视口
var Height = $(elem).offset().top - ($(document).scrollTop() + $(elem).height() + $(window).height());
if(Height < 0) {
//获取判断参数。为1则不执行,否则执行
var isDone = $(elem).attr('data-load');
if(!isDone) {
//ajax请求
getData(url, count, elem, type);
//设置判断参数
$(elem).attr('data-load', 1);
}
}
});
}
}
/**
* ajax请求数据
* @author 1
* @version version
* @date 2016-09-06
* @param {[string]} url [请求地址]
* @param {[number]} count [显示条数]
* @param {[string]} elem [dom元素]
* @return {[type]} [description]
*/
function getData(url, count, elem, type) {
$.ajax({
type:'get',
url:url,
dataType:'json',
success:function(msg) {
var str = '';
//判断count是否超过需要的条数
count = count > msg.length ? msg.length:count;
for(var i = 0; i < count; i++) {
//将数据加载到不同结构的dom中
str += domType(msg[i], type);
}
$(elem).append(str);
}
});
}
/**
* dom类型结构
* @author mao
* @version 1
* @date 2016-09-07
* @param {object} data 传入数据
* @param {number} type 选择的dom结构
*/
function domType(data, type) {
var str = '<li class="'+type+'">'+
'<a href="'+data.article_url+'" target="_blank">'+
'<img src="'+data.thumbnail_url+'">'+
'<div>'+
'<p>'+data.title+'</p>'+
'<span>'+data.digest+'</span>'+
'<abbr>'+data.published_at+'</abbr>'+
'</div>'+
'</a>'+
'</li>';
return str;
}
return mod;
})(utils || {});
这段代码出来之后基本是解决了提出的三个需求,满足实际的要求。对于前端同学的调用也是非常方便的。在跟同事分享之后,同事提了一个问题就是在绑定滚轮事件哪里。在每次调用的都会去绑定事件,相当于会在每一个调用的地方都会绑定一个回调。对于事件绑定的回调消耗会很大,这里可以改进一下。每次调用不去绑定,而是在所有的初始化参数函数准备好后再给予一次绑定,这样就不会有多次绑定事件回调的消耗问题。
为了解决这个问题,我一开始的思路是将事件绑定在以一个接口开放出来。在所有的函数初始化后再统一调用这个事件绑定。这样就需要将所有的参数暂时保存下来,在需要的时候再去拿。所以我想将他们保存在utils的内部,每次初始化参数一进来就保存。
修改的绑定部分如下:
mod.deferScroll = function() {
$(window).scroll(function() {
//对每一个dom元素进行绑定
$.each(obj, function(key, value) {
//dom块儿是否进入视口
var Height = $(value.elem).offset().top - ($(document).scrollTop() + $(value.elem).height() + $(window).height());
if(Height < 0) {
//获取判断参数。为1则不执行,否则执行
var isDone = $(value.elem).attr('data-load');
if(!isDone) {
//ajax请求
//console.log('视口延时', value);
getData(value);
//设置判断参数
$(value.elem).attr('data-load', 1);
}
}
});
});
}
上面代码中的obj就是保存下来的参数数组。
这样问题又来了,这样就会再向外部开放一个接口。都出来一组调用,对于调用使用就不是很方便了。所以想是否可以将这个函数放在内部,在调用的时候进入一次。所以想到可以这样
mod.dataDom = function(option) {
option.url = option.url,
option.count = option.count || 3,
option.elem = option.elem,
option.flag = option.flag || false,
option.type = option.type || '';
//存储数据
if(option.flag) {
obj.push(option);
} else {
//数据请求
getData(option);
//console.log('数据请求', option);
}
//让所有元素准备好,执行一次
if(!static_defer) {
//时间延时
deferTime();
//视口延时
deferScroll();
static_defer = true;
}
}
上面代码中多加了个内部变量static_defer,初始值设置为false。这样是指进行一次绑定,但是只有第一个初始化函数能够进入,后面的参数就没有绑定上。怎样才能让所有的参数准备好后才进行绑定。幸运的是javascript的事件循环,能够解决这个问题。将上述代码中加入setTimeout这个定时函数,会将setTimeout里的回调函数放入任务队列中,在主线程执行完之后再执行。这就使能所有的参数保存下来了再进行事件绑定。
修改如下:
mod.dataDom = function(option) {
option.url = option.url,
option.count = option.count || 3,
option.elem = option.elem,
option.flag = option.flag || false,
option.type = option.type || '';
//存储数据
if(option.flag) {
obj.push(option);
} else {
//数据请求
getData(option);
//console.log('数据请求', option);
}
//让所有元素准备好,执行一次
if(!static_defer) {
setTimeout(function() {
//时间延时
deferTime();
//视口延时
deferScroll();
}, 0);
static_defer = true;
}
}
这样不仅满足了需求,也对事件绑定进行了一次优化。
参考文档
JavaScript 运行机制详解:再谈Event Loop
The Node.js Event Loop, Timers, and process.nextTick()
Node.js Event Loop 的理解 Timers,process.nextTick()