概念

  • 声明在一个函数中的函数,叫做闭包函数。
  • 通常情况下,在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");
        }
    }
}

星不克
52 声望2 粉丝

一蓑烟雨任平生