防抖(debounce) 在学习单片机开发时,按键防抖 非常重要,我们必须使用一定的方法来进行防抖,否则会造成一次按键按键的多次触发的情况。而在JavaScript中,同样会有这样的问题,即在用户进行持续性的操作时,在用户大概率完成后,在进行后续操作(请求服务器,浏览器渲染等) 。如果不进行防抖操作,会导致服务器或浏览器的性能浪费。
其具体定义:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms ,然后:
- 如果在一段时间内没有再次触发滚动事件,那么就执行函数
- 如果在一段时间内再次触发滚动事件,那么当前的计时取消,重新开始计时
下面是一个函数实现:
1 2 3 4 5 6 7 8 9 10 11 12 function debounce (fun, delay ) { let timer return function (arguments ) { let that = this ; let args = arguments ; clearTimeout (timer) timer = setTimeout (function ( ) { fun.apply (that, args) }, delay) } }
该函数接受一个目标函数与延迟,该函数内部返回了一个函数,并将目标函数的id设为定时器的id。如果第一次调用该函数,会设置一个定时器,设定在delay毫秒后执行目标函数,但是如果在delay毫秒时间内再次调用该函数的防抖函数,那么就会清除掉目标函数的定时器,也就不会执行了。并再次设置一个定时器,循环以上步骤。
节流( Throttle) 节流更是一个重要的概念,特别是在进行渲染的操作时(比如,mousemove,scoll) ,如果不进行节流,那么浏览器将会消耗很多性能,致使页面卡顿与操作不流畅。
具体定义:是指在一定的时间内只允许函数执行特定次数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function throttle (fn,delay ){ let _self = fn, timer, firstTime = true ; return function ( ){ let args = arguments _this = this if (firstTime){ _self.apply (_this, args) return firstTime = false } if (timer){ return false } timer = setTimeout (function ( ){ clearTimeout (timer) timer = null _self.apply (_this, args) }, delay||500 ) } }
该函数的核心是在:在外部先判断目标函数fun的timer属性是否为空。如果为空,则代表一次函数还未执行完,则直接返回。否则则设定目标函数的timer属性为一个定时器,在定时器的函数中将timer清空,并执行目标函数。
2021/2/17 最近在读《JavaScript设计模式与开发实践》,发现除了函数的节流与防抖,还有一些高级函数模式,可以用来解决一些问题。
分时函数 当一个任务需要进行大量同类操作,此时可能会造成页面的卡顿。此时,为了防止页面卡顿,我们要把单个大型任务分割成为多个小的任务,典型的场景是:大量数据插入表格。
我们把但分时函数代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let timeChunk = function (ary, fn, count ){ let obj let t let len = ary.length let start = function ( ){ for (let i = 0 ; i < Math .min (count || 1 , ary.length ); i++){ let obj = ary.shift () fn (obj) } } return function ( ){ t = setInterval (function ( ){ if (ary.length === 0 ){ return clearInterval (t) } start () }, 200 ) } }
比如我们需要添加1000调信息到页面中,我们采用每次创建8个节点。
1 2 3 4 5 6 7 8 9 10 11 12 let ary = []for (let i = 1 ; i <= 1000 ; i++){ ary.push (i) } let renserList = timeChunk (ary, function (n ){ let div = document .createElement ('div' ) dic.innerHTML = n document .body .appenChild (div) }, 8 ) renderList ()
惰性加载函数 当一个任务在执行之前后悔事先执行一些预先性工作,并且这些工作在每次执行时的结果都是一样的,那我们可以在第一次得到结果后就将其凝固,在以后的每一次执行时就可以减少判断次数,提高性能。
一个典型的场景就是浏览器能力检测,常见的写法如:
1 2 3 4 5 6 7 8 let addEvent = function (elem, type, handler ){ if (window .addEventListener ){ return elem.addEventListener (type, handler, false ) } if (window .attachEvent ){ return elem.attachEvent ('on' + type, handler) } }
这个函数的缺点是,当它每次被调用的时候都会执行里面的if
条件分支,虽然执行这些if
分支的开销不算大,但也许有一些方法可以让程序避免这些重复的执行条件。如下面的方案就是在最开始调用一次判断,后续调用该函数时,就不会再进行判断了:
1 2 3 4 5 6 7 8 9 10 11 12 let addEvent = (function ( ){ if (window .addEventListener ){ return function (elem, type, handler ){ elem.addEventListener (type, handler, false ) } } if (window .attachEvent ){ return function (elem, type, handler ){ return elem.attachEvent ('on' + type, handler) } } })()
第三种方案就是我们的懒加载函数 ,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 let addEvent = function (elem, type, handler ){ if (window .addEventListener ){ addEvent = function (elem, type, handler ){ elem.addEventListener (type, handler, false ) } }else if (window .attachEvent ){ addEvent = function (elem, type, handler ){ elem.attachEvent ('on' +type, handler) } } addEvent (elem, type, handler) }
这个函数在第一次调用时,会根据条件分支,对addEvent
事件进行重载,并且对其进行调用一次。这个函数不必判断第一次,而是自动在第一次时重载,很妙。
这里有一个知识点是,在具名函数的执行过程中是可以对具名函数进行重新赋值的。
summary 总体来说,在进行某些操作时,比如表单实时验证或者绘图的操作等,函数的防抖与节流会大幅度节省服务器与浏览器的性能!