JS中setTimeout的巧妙用法前端函数节流
什么是函数节流?
函数节流简单的来说就是不想让该函数在很短的时间内连续被调用,比如我们最常见的是窗口缩放的时候,经常会执行一些其他的操作函数,比如发一个ajax请求等等事情,那么这时候窗口缩放的时候,有可能连续发多个请求,这并不是我们想要的,或者是说我们常见的鼠标移入移出tab切换效果,有时候连续且移动的很快的时候,会有闪烁的效果,这时候我们就可以使用函数节流来操作。大家都知道,DOM的操作会很消耗或影响性能的,如果是说在窗口缩放的时候,为元素绑定大量的dom操作的话,会引发大量的连续计算,比如在IE下,过多的DOM操作会影响浏览器性能,甚至严重的情况下,会引起浏览器崩溃的发生。这个时候我们就可以使用函数节流来优化代码了~
函数节流的基本原理:
使用一个定时器,先延时该函数的执行,比如使用setTomeout()这个函数延迟一段时间后执行函数,如果在该时间段内还触发了其他事件,我们可以使用清除方法clearTimeout()来清除该定时器,再setTimeout()一个新的定时器延迟一会儿执行。
最近在某团队忙于一个项目,有这么一个页面,采用传统模式开发(吐槽它为什么不用React),它的DOM操作比较多,然后性能是比较差的,尤其当你缩放窗口时,可怕的事情发生了,出现了卡顿,甚至浏览器瘫痪。为什么呢?
由于该页面的DOM操作非常多,故窗口缩放每一帧时都会触发函数的执行,连续的重新DOM操作,这样对浏览器的开销是非常大的。既然在窗口缩放时,会让浏览器重新计算DOM,那么我们为什么不可以让DOM的计算延时呢,让窗口停止缩放后才重新计,这样不就节省了浏览器的开销,达到优化的效果了吗?
知识准备
1.setTimeout(code,millisec)当然就是本文的主角了。
setTimeout()方法用于在指定的毫秒数后调用函数或计算表达式。
code必需。要调用的函数后要执行的JavaScript代码串。
millisec必需。在执行代码前需等待的毫秒数。
提示:setTimeout()只执行code一次。如果要多次调用,请使用setInterval()或者让code自身再次调用setTimeout()。
广泛应用于定时器,轮播图,动画效果,自动滚动等等。
2.clearTimeout(id_of_setTimeout)
参数id_of_settimeout由setTimeout()返回的ID值。该值标识要取消的延迟执行代码块。
3.fun.apply(thisArg[,argsArray])
apply()方法在指定this值和参数(参数以数组或类数组对象的形式存在)的情况下调用某个函数
该函数的语法与call()方法几乎相同,唯一的区别在于,call()方法接受的是一个参数列表,而apply()接受的是一个包含多个参数数组的(或类数组对象)。
参数
thisArg
在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null或undefined时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
argsArray
一个数组或者类数组对象,其中的数组元素将作为单独的参数传给fun函数。如果该参数的值为null或undefined,则表示不需要传入任何参数。从ECMAScript5开始可以使用类数组对象。
在调用一个存在的函数时,你可以为其指定一个this对象。this指当前对象,也就是正在调用这个函数的对象。使用apply,你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
4.fun.call(thisArg[,arg1[,arg2[,...]]])
该方法在使用一个指定的this值和若干个指定的参数值的前提下调用某个函数或方法.
参数
thisArg
在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
arg1,arg2,...
指定的参数列表。
当调用一个函数时,可以赋值一个不同的this对象。this引用当前对象,即call方法的第一个参数。通过call方法,你可以在一个对象上借用另一个对象上的方法,比如Object.prototype.toString.call([]),就是一个Array对象借用了Object对象上的方法。
作用:
使用call方法调用父构造函数
使用call方法调用匿名函数
使用call方法调用匿名函数并且指定上下文的'this'
这里插个题外话:
apply与call()非常相似,不同之处在于提供参数的方式。apply使用参数数组而不是一组参数列表。apply可以使用数组字面量(arrayliteral),如fun.apply(this,['eat','bananas']),或数组对象,如fun.apply(this,newArray('eat','bananas'))。你也可以使用arguments对象作为argsArray参数。arguments是一个函数的局部变量。它可以被用作被调用对象的所有未指定的参数。这样,你在使用apply函数的时候就不需要知道被调用对象的所有参数。你可以使用arguments来把所有的参数传递给被调用对象。被调用对象接下来就负责处理这些参数。
从ECMAScript第5版开始,可以使用任何种类的类数组对象,就是说只要有一个length属性和[0...length)范围的整数属性。例如现在可以使用NodeList或一个自己定义的类似{'length':2,'0':'eat','1':'bananas'}形式的对象。
call,apply方法区别是,从第二个参数起,call方法参数将依次传递给借用的方法作参数,而apply直接将这些参数放到一个数组中再传递,最后借用方法的参数列表是一样的.
应用场景:当参数明确时可用call,当参数不明确时可用apply给合arguments
现在先给出一个例子
总所皆知,onscolll,onresize等是非常耗性能,窗口缩放时打印数字。
varcount=; window.onresize=function(){ count++; console.log(count); }
在chrome浏览器中伸缩浏览器窗口大小,打印如下
这显然不是我们想要的,那如果我们换成ajax请求的话,那么就会缩放一次窗口会连续触发多次ajax请求,下面我们试着使用函数节流的操作试试一下;当然加个settimeout()的定时器就好了,
第一种封装方法
varcount=; functionoCount(){ count++; console.log(count); } window.onresize=function(){ delayFun(oCount) }; functiondelayFun(method,thisArg){ clearTimeout(method.props); method.props=setTimeout(function(){ method.call(thisArg) },) }
第二种封装方法
构造一个闭包,使用闭包的方式形成一个私有的作用域来存放定时器timer,timer是通过传参数的形式引入的。
varcount=; functionoCount(){ count++; console.log(count); } varfuns=delayFun(oCount,); window.onresize=function(){ funs() }; functiondelayFun(func,wait){ vartimer=null; returnfunction(){ varcontext=this, args=arguments; clearTimeout(timer); timer=setTimeout(function(){ func.apply(context,args); },wait) }; }
对第二种方法优化一下,性能会更好
这里返回一个函数,如果它被不间断地调用,它将不会得到执行。该函数在停止调用N毫秒后,再次调用它才会得到执行。如果有传递‘immediate'参数,会马上将函数安排到执行队列中,而不会延迟。
functiondelayFun(func,wait,immediate){ vartimeout; returnfunction(){ varcontext=this,args=arguments; varlater=function(){ timeout=null; if(!immediate)func.apply(context,args); }; varcallNow=immediate&&!timeout; clearTimeout(timeout); timeout=setTimeout(later,wait); if(callNow)func.apply(context,args); }; }; //用法 varmyEfficientFn=delayFun(function(){ //所有繁重的操作 },); window.addEventListener('resize',myEfficientFn);
函数不允许回调函数在指定时间内执行多于一次。当为一个会频繁触发的事件分配一个回调函数时,该函数显得尤为重要。
setTimeout这么厉害,那么我们是可以在项目中大量使用吗?
我个人是不建议的,在我们业务中,基本上是禁止在业务逻辑中使用setTimeout的,因为我所看到的很多使用方式都是一些问题好解决,setTimeout作为一个hack的方式。
例如,当一个实例还没有初始化的前,我们就使用这个实例,错误的解决办法是使用实例时加个setTimeout,确保实例先初始化。
为什么错误?这里其实就是使用hack的手段
第一是埋下了坑,打乱模块的生命周期
第二是出现问题时,setTimeout其实是很难调试的。
我认为正确的使用方式是,看看生命周期(可参考《关于软件的生命周期》),把实例化提到使用前执行。
有关JS中setTimeout的巧妙用法前端函数节流,小编就给大家介绍到这里,希望对大家有所帮助!