问题的引出
在一些场景往往由于事件频繁被触发,因而频繁地进行DOM操作、资源加载,导致UI停顿甚至浏览器崩溃。
在这样的情况下,我们实际上的需求大多为停止改变大小n毫秒后执行后续处理;而其他事件大多的需求是以一定的频率执行后续处理。针对这两种需求就出现了debounce和throttle两种解决办法。
1. resize事件
2. mousemove事件
3. touchmove事件
4.scroll事件
throttle 与 debounce
在现在很多的javascript框架中都提供了这两个函数。例如 jquery中有throttle和debounce插件, underscore.js ,Lodash.js 等都提供了这两个函数。
原理:
首先我们会想到设置一定的时间范围delay,每隔delayms 执行不超过一次。
事件处理函数什么时候执行能? 这里有两个选择,一是先执行,再间隔delayms来等待;或者是先等待delayms,然后执行事件处理函数。
操作过程中的事件全不管,反正只执行一次事件处理。
相同低,这一次的事件处理可以是先执行一次,然后后面的事件都不管; 或者前面的都不管,最后操作完了再执行一次事件处理。
区别:
1. throttle
如果将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。
也就是会说预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。
2.debounce
如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。
也就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。
简单代码实现及实验结果
那么下面我们自己简单地实现下这两个函数:
throttle 函数:
window.addEventListener("resize", throttle(callback, 300, {leading:false})); window.addEventListener("resize", callback2); function callback () { console.count("Throttled"); } function callback2 () { console.count("Not Throttled"); } /** * 频率控制函数, fn执行次数不超过 1 次/delay * @param fn{Function} 传入的函数 * @param delay{Number} 时间间隔 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false}, * 如果想忽略结束边界上的调用则传入 {trailing:false}, * @returns {Function} 返回调用函数 */ function throttle(fn,delay,options) { var wait=false; if (!options) options = {}; return function(){ var that = this,args=arguments; if(!wait){ if (!(options.leading === false)){ fn.apply(that,args); } wait=true; setTimeout(function () { if (!(options.trailing === false)){ fn.apply(that,args); } wait=false; },delay); } } }
将以上代码贴入浏览器中运行,可得到:
下面再看debounce函数的情况,
debounce 函数:
window.addEventListener("resize", throttle(callback, 300, {leading:false})); window.addEventListener("resize", callback2); function callback () { console.count("Throttled"); } function callback2 () { console.count("Not Throttled"); } /** * 空闲控制函数, fn仅执行一次 * @param fn{Function} 传入的函数 * @param delay{Number} 时间间隔 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false}, * 如果想忽略结束边界上的调用则传入 {trailing:false}, * @returns {Function} 返回调用函数 */ function debounce(fn, delay, options) { var timeoutId; if (!options) options = {}; var leadingExc = false; return function() { var that = this, args = arguments; if (!leadingExc&&!(options.leading === false)) { fn.apply(that, args); } leadingExc=true; if (timeoutId) { clearTimeout(timeoutId); } timeoutId = setTimeout(function() { if (!(options.trailing === false)) { fn.apply(that, args); } leadingExc=false; }, delay); } }
将以上代码贴入浏览器中运行,分三次改变窗口大小,可看到,每一次改变窗口的大小都会把开始和结束边界的事件处理函数各执行一次:
如果是一次性改变窗口大小,会发现开始和结束的边界各执行一次时间处理函数,请注意与一次性改变窗口大小时 throttle 情况的对比:
underscore.js 的代码实现
_.throttle函数
/** * 频率控制函数, fn执行次数不超过 1 次/delay * @param fn{Function} 传入的函数 * @param delay{Number} 时间间隔 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false}, * 如果想忽略结束边界上的调用则传入 {trailing:false}, * @returns {Function} 返回调用函数 */ _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false "htmlcode">/** * 空闲控制函数, fn仅执行一次 * @param fn{Function} 传入的函数 * @param delay{Number} 时间间隔 * @param options{Object} 如果想忽略开始边界上的调用则传入 {leading:false}, * 如果想忽略结束边界上的调用则传入 {trailing:false}, * @returns {Function} 返回调用函数 */ _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last > 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };参考的文章
Debounce and Throttle: a visual explanation
jQuery throttle / debounce: Sometimes, less is more!
underscore.js以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。