咱们来看一个分析:
若是要实现一个拖拽功能,须要一路监听 mousemove 事件,在回调中获取元素当前位置,而后重置dom的位置来进行样式改变。若是不加以控制,每移动必定像素而触发的回调数量很是惊人,回调中又伴随着 DOM 操做,继而引起浏览器的重排与重绘,性能差的浏览器可能就会直接假死.html
在某些状况下会引起函数被很是频繁地调用,而形成大的性能问题。解决性能问题的处理办法就是函数节流和函数防抖。前端
是函数在特定的时间内不被再调用后执行。 也就是让某个函数在上一次执行后,知足等待某个时间内再也不触发此函数后再执行,而在这个等待时间内再次触发此函数,等待时间会从新计算。ajax
var timer = false; document.querySelector("#div").mousemove = function(){ clearTimeout(timer); // 当事件触发的时候,清除以前等待执行的函数, timer = setTimeout(function(){ // 开启新的延时执行函数 console.log("函数防抖"); }, 300); };
咱们如今对上述方法封装一下浏览器
// 函数防抖 const debounce = function(fn, wait=300){ return function(){ clearTimeout(fn.timer); // 当事件触发的时候,清除以前等待执行的函数, fn.timer = setTimeout(fn, 300); } }
可是咱们就会发现 fn 的this 指向发生了改变, fn参数是接收不到的。app
function debounce = function(fn, wait=300){ return function(){ clearTimeout(fn.timer); // 当事件触发的时候,清除以前等待执行的函数, fn.timer = setTimeout(fn.bind(this), wait); } }
或者使用applydom
function debounce = function(fn, wait=100){ return function(){ clearTimeout(fn.timer); // 当事件触发的时候,清除以前等待执行的函数, fn.timer = setTimeout( ()=>{ // 不能写匿名函数,this会发生改变 return fn.apply(this, arguments); }, wait); } }
函数节流,即限制函数的执行频率,在持续触发事件的状况下,间断地执行函数。只要当前函数没有执行完成,任何新触发的函数都会被忽略。就是在固定的时间间隔内函数只会被调用一次。异步
var timer = false; document.querySelector("#div").mousemove = function(){ if(timer) return; timer = setTimeout(function(){ // 正在执行 console.log("函数节流"); timer = false; // 表示不在执行 }, 100); };
在来改写一下:函数
const throttle = function(fn, wait=100){ return function(){ if(fn.timer){ return; } fn.timer = setTimeout(()=>{ fn.apply(this,arguments); fn.timer = false; },wait) } }
函数的节流和函数的去抖都是经过减小实际逻辑处理过程的执行来提升事件处理函数运行性能的手段,并无实质上减小事件的触发次数。
某些函数确实是用户主动调用的,但由于一些客观的缘由,这些函数会严重地影响页面性能。解决方案:例如分页技术和延迟加载,均可以免在页面上同时加载过多数据,形成的页面卡顿。甚至假死现象。其实,优化就是合理利用性能。性能
underscore v1.7.0
相关的源码实现_.debounce = function(func, wait, immediate) { // immediate默认为false var timeout, args, context, timestamp, result; var later = function() { // 当wait指定的时间间隔期间屡次调用_.debounce返回的函数,则会不断更新timestamp的值,致使last < wait && last >= 0一直为true,从而不断启动新的计时器延时执行func 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(); // 第一次调用该方法时,且immediate为true,则调用func函数 var callNow = immediate && !timeout; // 在wait指定的时间间隔内首次调用该方法,则启动计时器定时调用func函数 if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; };
_.throttle = function(func, wait, options) { /* options的默认值 * 表示首次调用返回值方法时,会立刻调用func;不然仅会记录当前时刻,当第二次调用的时间间隔超过wait时,才调用func。 * options.leading = true; * 表示当调用方法时,未到达wait指定的时间间隔,则启动计时器延迟调用func函数,若后续在既未达到wait指定的时间间隔和func函数又未被调用的状况下调用返回值方法,则被调用请求将被丢弃。 * options.trailing = true; * 注意:当options.trailing = false时,效果与上面的简单实现效果相同 */ var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; // 计算剩余时间 var remaining = wait - (now - previous); context = this; args = arguments; // 当到达wait指定的时间间隔,则调用func函数 // 精彩之处:按理来讲remaining <= 0已经足够证实已经到达wait的时间间隔,但这里还考虑到假如客户端修改了系统时间则立刻执行func函数。 if (remaining <= 0 || remaining > wait) { // 因为setTimeout存在最小时间精度问题,所以会存在到达wait的时间间隔,但以前设置的setTimeout操做还没被执行,所以为保险起见,这里先清理setTimeout操做 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // options.trailing=true时,延时执行func函数 timeout = setTimeout(later, remaining); } return result; }; };