本篇课题,或许早已经是烂大街的解读文章。不过春招系列面试下来,很多伙伴们仍是似懂非懂地栽倒在(~面试官~)深意的笑容之下,权当温故知新。javascript
JavaScript的执行过程,是基于栈来进行的。复杂的程序代码被封装到函数中,程序执行时,函数不断被推入执行栈中。因此 "执行栈" 也称 "函数执行栈"。前端
函数中封装的代码块,通常都有相对复杂的逻辑处理(计算/判断),例如函数中可能会涉及到 DOM
的渲染更新,复杂的计算与验证, Ajax
数据请求等等。java
前端页面的操做权,大部分都是属于浏览端的客户爸爸们(单身三十年的手速,惹不起惹不起!!!)。若是函数被频繁调用,形成的性能开销绝对不仅一点点。面试
DOM
频繁重绘的卡顿让客户爸爸们想把你揪出来一顿大招。。。既要提高用户体验,又要减小后端服务开销,可见咱们大前端的使命不仅一页PPT。说好前因,接着就是后果了。既然有优化的需求,必然就要有相应的解决方案。隆重请出主角: “防抖” 与 “节流”。后端
在事件被触发 n 秒后再执行回调函数,若是在这 n 秒内又被触发,则从新计时延迟时间。性能优化
生活化理解:英雄的技能条,技能条读完才能使用技能(R大招60s)app
防抖的实现方式分两种 “当即执行” 和 “非当即执行”,区别在于第一次触发时,是否当即执行回调函数。函数
”非当即执行防抖“ 指事件触发后,回调函数不会当即执行,会在延迟时间 n 秒后执行,若是 n 秒内被调用屡次,则从新计时延迟时间工具
// e.g. 防抖 - 非当即执行 function debounce(func, delay) { var timeout; return function() { var context = this; var args = arguments; // && 短路运算 == if(timeout) else {...} timeout && clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args); }, delay); } } // 调用 var printUserName = debounce(function(){ console.log(this.value); }, 800); document.getElementById('username') .addEventListener('keyup', printUserName);
“当即执行防抖” 指事件触发后,回调函数会当即执行,以后要想触发执行回调函数,需等待 n 秒延迟post
// e.g. 防抖 - 当即执行 function debounce(func, delay) { var timeout; return function() { var context = this; var args = arguments; callNow = !timeout; timeout = setTimeout(function() { timeout = null; }, delay); callNow && func.apply(context, args); } }
函数防抖原理:经过维护一个定时器,其延迟计时以最后一次触发为计时起点,到达延迟时间后才会触发函数执行。
规定在一个单位时间内,只能触发一次函数。若是这个单位时间内触发屡次函数,只有一次生效(间隔执行)
生活化理解:
函数节流实现的方式有 “时间戳” 和 “定时器” 两种。
// e.g. 节流 - 时间戳 function throttle(func, delay) { var lastTime = 0; return function() { var context = this; var args = arguments; var nowTime = +new Date(); if (nowTime > lastTime + delay) { func.apply(context, args) lastTime = nowTime; } } }
“时间戳” 的方式,函数在时间段开始时执行。
缺点:假定函数间隔1s执行,若是最后一次中止触发,卡在4.2s,则不会再执行。
// e.g. 节流 - 定时器 function throttle(func, delay) { var timeout; return function() { var context = this; var args = arguments; if (!timeout) { setTimeout(function(){ func.apply(context, args); timeout = null; }, delay) } } }
“定时器” 的方式,函数在时间段结束时执行。可理解为函数并不会当即执行,而是等待延迟计时完成才执行。(因为定时器延时,最后一次触发后,可能会再执行一次回调函数)
// e.g. 节流 - 时间戳 + 定时器 function throttle(func, delay) { let lastTime, timeout; return function() { let context = this; let args = arguments; let nowTime = +new Date(); if (lastTime && nowTime < lastTime + delay) { timeout && clearTimeout(timeout); timeout = setTimeout(function(){ lastTime = nowTime; func.apply(context, args); }, delay); } else { lastTime = nowTime; func.apply(context, args); } } }
合并优化的原理:“时间戳”方式让函数在时间段开始时执行(第一次触发当即执行),“定时器”方式让函数在最后一次事件触发后(如4.2s)也能触发。
函数节流原理:必定时间内只触发一次,间隔执行。经过判断是否到达指定触发时间,间隔时间固定。
相同:都是防止某一时间段内,函数被频繁调用执行,经过时间频率控制,减小回调函数执行次数,来实现相关性能优化。
区别:“防抖”是某一时间内只执行一次,最后一次触发后过段时间执行,而“节流”则是间隔时间执行,间隔时间固定。
scroll
resize
mousemove
拖拽应用场景还有不少,具体场景需具体分析。只要涉及高频的函数调用,均可参考函数防抖节流的优化方案。
鼓起勇气写在结尾:以上代码都不是 “完美” 的 “防抖 / 节流” 实现代码!!!仅就实现方式和基本原理,浅谈分解一二。
实际代码开发中,通常会引入lodash
相对 “靠谱” 的第三方库,帮咱们去实现防抖节流的工具函数。有兴趣的伙伴们可阅读 lodash
相关源码,加深印象理解可再读如下参考文章。
参考文章