在浏览器中操作DOM比非DOM交互需要更多的内存和CPU的事件,连续尝试进行过多的DOM相关操作可能UI导致浏览器挂起,有时甚至会崩溃。尤其在IE中使用onresize事件处理程序的时候容易发生,当调整浏览器大小的时候,该事件会连续触发。在onresize事件处理程序内部如果尝试进行DOM操作,其高频率的更改可能会让浏览器崩溃。为了解决短时间内重复调用事件的这个问题,可以使用函数节流、函数防抖和设置标识变量的方法
函数节流
函数节流背后的思想是指某些代码不可以在没有间断的情况下连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。实现函数的基本模式如下
var processor = {
timeoutId: null,
performProcessing:function(){
//实际执行的代码
console.log('eee');
},
percess: function(){
clearTimeout(this.timeoutId);
var that = this;
this.timeoutId = setTimeout(function(){
that.performProcessing();
},200)
}
}
该函数的主要意思是,当在200ms以内再次触发performProcessing事件时,就删除触发该事件的定时器并重新新建一个触发该事件的定时器。当最后一次调用process之后至少200ms后才会调用performProcessing()。 该模式的简化形式如下
function throttle(method,context){
clearTimeout(method.tId);
method.tId = setTimeout(function(){
method.call(context);
},200);
}
该函数接收两个参数:要执行的函数以及在哪个作用域中执行。首先清除之前设置的任何定时器。定时器ID是存储咋函数的tId属性中的,第一次吧方法传递给torottle()的时候,该属性并不存在。接下来创建定时器,并将ID存储在方法的tID属性中。如果这是第一次对这个方法调用throttle()的话,那么这段代码会创建该属性。定时器会使用call()来确定方法在适当的环境中执行。如果没有给出第二个参数,那么就在全局作用域中执行
我们使用上述简化方法实现resize事件的节流。下面函数主要实现当改变窗口尺寸的时候
function resizeDiv(){
var div = document.getElementById('myDiv');
div.style.height = div.offsetWidth + 'px';
}
window.onresize = function(){
throttle(resizeDiv);
}
函数防抖
如果一直触发一个事件,函数节流的方法定义的方法永远不会执行,就像一个水龙头,如果关闭了,永远不会出水;函数防抖的方法像一个关闭水龙头不好用,当关闭水龙头隔一段时间会有滴水一样。就是一个事件如果频繁触发,会隔一段时间执行一次。
函数防抖的实现方式如下
var debounce = function(fn,delay,mustRunDelay){
var timer = null;
var t_start;
return function(){
var context = this;
var args = arguments;
var t_curr = +new Date();
clearTimeout(timer);
if(!t_start){
t_start = t_curr;
}
if(t_curr - t_start >= mustRunDelay) {
fn.apply(context,args);
t_start = t_curr
} else {
timer = setTimeout(function(){
fn.apply(context,args);
},delay);
}
}
}
该函数接收三个参数,分别为要执行的函数,隔多长时间清除函数定时器以及多长时间需要执行一次。
如果利用函数防抖实现resize事件,实现方法如下
window.onresize = throttleV2(resizeDiv,50,100);
标识变量法
在项目中需要使用scroll事件,当scroll滚动到页面底部时,发送请求验证还有没有其他的资源需要加载。如果直接使用scroll事件,当滚动到文档底部发送ajax请求的话,ajax请求会连续触发。为了解决这个问题,设置一个初始值为true的标志变量,只有标识变量为true时才能发送ajax请求。当发送ajax请求时,将标志变量设为false,收到请求的响应并处理完后将标志变量设为true。整个代码如下
var pageIndex = {
newPage : 1,
hotPage:1
}
var scrollFlag = {
newFlag: true,
hotFlag: true,
}
EventUtil.addHandler(window,'scroll',function(){
scrollE(newPost,'new');
})
function scrollE(ele,str){
var totalHeight = document.documentElement.scrollTop + document.body.scrollTop + document.documentElement.clientHeight ;
if(totalHeight > ele.offsetTop + ele.offsetHeight) {
if(scrollFlag[str + "Flag"]){
scrollFlag[str + "Flag"] = false ;
if(pageIndex[str + "Page"]< 10) {
pageIndex[str + "Page"]++;
var xhr=createXHR();
var postHtml = ele.innerHTML;
xhr.onreadystatechange=function(){
if(xhr.readyState==1){
ele.innerHTML = postHtml + '<div class = "loading"><div class="load-info"><span class="load-img"></span>我去拿数据 等我一会儿</div></div>'
}
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
var res = xhr.responseText;
res = JSON.parse(res);
ele.innerHTML = postHtml + res.htmlres;
if(res[str+'IsLastPage']){
ele.innerHTML = ele.innerHTML+'<div class = "final-page"><span>别拉啦 我已经加载完了<span></div>'
} else {
scrollFlag[str + "Flag"] = true;
}
}else{
console.log("request was unsuccessful:"+xhr.status);
}
}
}
xhr.open('GET','postpage/' + str + '/' + pageIndex[str + "Page"],true);
xhr.send();
}
}
}
}