乐趣区

关于javascript:理解JS中闭包及实际应用防抖debounce和节流throttling

概念

  • 申明在一个函数中的函数,叫做闭包函数。
  • 通常状况下,在 Javascript 语言中,只有函数外部的子函数能力读取局部变量:
function f1(){var n = 999;}
console.log(n); // n is not defined
  • 闭包是一种非凡的作用域:其返回的外部函数的作用域中保留着父级的变量对象和作用域连贯,所以外部函数总是能够拜访其所在的内部函数中申明的参数和变量,即便在其内部函数被销毁之后。
  • 所以,在实质上,闭包就是将函数外部和函数内部连接起来的一座桥梁。

特点

  • 让内部拜访函数外部变量成为可能
  • 局部变量会常驻在内存中
  • 能够防止应用全局变量,避免全局变量净化
  • 会造成内存透露(有一块内存空间被长期占用,而不被开释)

利用场景实例

防抖 debounce

  • 构想有此场景:输入框中内容变动须要实时申请接口以获取最新搜寻后果,如果在输出实现前输入框内容每变动一下都去申请接口,会造成很多不必要的申请,大大增加服务器压力。
  • 解决思路:有变动时提早一段时间再执行 function,若在这段延迟时间内又有新变动,则从新开始提早
  // 定时器期间,有新操作时,清空旧定时器,重设新定时器
  var debounce = (fn, wait) => {
     let timer, timeStamp=0;
     let context, args;
    
      let run = ()=>{timer= setTimeout(()=>{fn.apply(context,args);
         },wait);
     }
    
     let clean = () => {clearTimeout(timer);
      }

     return function() {
        context = this;
        args = arguments;
        let now = (new Date()).getTime();
        if (now-timeStamp < wait) {console.log('reset',now);
            // 革除定时器,并重新加入提早 
            clean(); 
            run();} else {console.log('set',now);
            run();  // last timer alreay executed, set a new timer}
         timeStamp = now;
      }
  }
  • 代码进一步优化:周期内有新事件触发时,重置定时器开始工夫戳,定时器执行时,判断开始工夫戳,若开始工夫戳被推后,从新设定延时定时器;退出是否立刻执行参数。
// 减少前缘触发性能
var debounce = (fn, wait, immediate=false) => {
    let timer, startTimeStamp=0;
    let context, args;
 
    let run = (timerInterval) => {timer= setTimeout(() => {let now = (new Date()).getTime();
            let interval = now-startTimeStamp
            if(interval < timerInterval) { // the timer start time has been reset,so the interval is less than timerInterval
                console.log('debounce reset',timerInterval-interval);
                startTimeStamp = now;
                run(wait-interval);  // reset timer for left time 
            } else {if (!immediate) {fn.apply(context,args);
                }
                clearTimeout(timer);
                timer=null;
            }
        }, timerInterval);
    }
 
    return function() {
        context = this;
        args = arguments;
        let now = (new Date()).getTime();
        startTimeStamp = now; // set timer start time
 
        if(!timer) {console.log('debounce set',wait);
            if(immediate) {fn.apply(context,args);
            }
            run(wait);    // last timer alreay executed, set a new timer
        }    
    }
}

节流 throttling

  • 构想有此场景:有‘搜寻’按钮,每点击一次都会从新申请接口,获取并渲染页面表格最新数据,如果短时间内间断点击按钮,仍然会造成很多不必要的申请
  • 解决思路:在一段时间内只执行最初一次 function
// 定时器期间,只执行最初一次操作
var throttling = (fn, wait) => {
    let timer;
    let context, args;
 
    let run = () => {timer=setTimeout(()=>{fn.apply(context,args);
            clearTimeout(timer);
            timer=null;
        },wait);
    }
 
    return function () {
        context=this;
        args=arguments;
        if(!timer){console.log("throttle, set");
            run();}else{console.log("throttle, ignore");
        }
    }
}
// 减少前缘
var throttling = (fn, wait, immediate) => {
    let timer, timeStamp=0;
    let context, args;
 
    let run = () => {timer=setTimeout(()=>{if(!immediate){fn.apply(context,args);
            }
            clearTimeout(timer);
            timer=null;
        },wait);
    }
 
    return function () {
        context=this;
        args=arguments;
        if(!timer){console.log("throttle, set");
            if(immediate){fn.apply(context,args);
            }
            run();}else{console.log("throttle, ignore");
        }
    }
}
退出移动版