javaScript中的闭包以及闭包的经典案例

前言

早在之前我对闭包还不是很理解的时候,我把它抽象的认为是函数里面写函数,有点滑稽。其实闭包的正确定义是,可以访问另外函数作用域内部变量的函数。这样它就可以重复使用变量,且不会造成变量污染。

什么是变量污染?先看一段经典代码,在那个没有三大框架的年代,要给列表添加点击事件时:

const list = document.querySelector('li');
for (var i = 0; i < list.length - 1; i++) {
  list[i].onclick = function(i) {
    console.log(i);
  }
}
// 为了方便执行,用setTimeout函数代替给dom添加点击事件
for (var i = 0; i < 5; i++) {
  console.log('直接打印', i);
  setTimeout(() => { console.log('延迟打印', i) });
}
// 直接打印 0
// 直接打印 1
// 直接打印 2
// 直接打印 3
// 直接打印 4
// 延迟打印 5
// 延迟打印 5
// 延迟打印 5
// 延迟打印 5
// 延迟打印 5

这样写的问题就是,无论你点击哪个

  • 标签,总是打印5,这是因为使用var声明变量时,会创建全局变量;上面的代码类似于这样:

    var i = 0;
    for (i < 5; i++) {
      console.log('直接打印', i);
      setTimeout(() => { console.log('延迟打印', i) });
    }
    

    其实每次循环都是在操作同一个变量,这就导致了变量污染,等到触发点击事件时,这个i就已经等于5了。我还记得我当时是这样处理的,使用一个立即执行函数包裹一下,这样就通过立即执行函数创造了一个局部作用域,保存住了每次循环的变量i,每个作用域块内的变量不会互相污染:

    for (var i = 0; i < 5; i++) {
      console.log('直接打印', i);
      (function(i) {
        setTimeout(() => { console.log('延迟打印', i) });
      })(i);
    }
    // 直接打印 0
    // 直接打印 1
    // 直接打印 2
    // 直接打印 3
    // 直接打印 4
    // 延迟打印 0
    // 延迟打印 1
    // 延迟打印 2
    // 延迟打印 3
    // 延迟打印 4
    

    但是现在有了let,这个问题就简单得多了,因为使用let声明变量时自带作用域,其实for循环表达式部分是一块单独的作用域,其内部的i是继承了父作用域的i值:

    for (let i = 0; i < 5; i++) {
      console.log('直接打印', i);
      setTimeout(() => { console.log('延迟打印', i) });
    }
    // 直接打印 0
    // 直接打印 1
    // 直接打印 2
    // 直接打印 3
    // 直接打印 4
    // 延迟打印 0
    // 延迟打印 1
    // 延迟打印 2
    // 延迟打印 3
    // 延迟打印 4
    

    而且Vue有了v-for指令,想给列表加点击事件,直接在标签上添加就可以了。
    其实在我们使用立即执行函数保存变量时,就是在使用闭包了。

    而如今,闭包应用很经典的例子就是去抖节流

    1.去抖

    去抖函数是应用于短时间内连续多次调用同一个函数场景。我们用一个延迟函数去阻止它立即执行,当短时间内再次调用,我们清除上一个延迟函数,重新创建一个延迟函数,直到最后不再调用目标函数,则延迟时间到达执行一次目标函数。应用场景就是列表筛选,需求为输入内容改变就调用接口筛选列表,当连续输入内容时,我们不能一直调用接口,只在用户输入完调用一次接口即可,这样可以大大降低网络开销。

    const debounce = (fn, ms = 500) => {
      let timer = null;
      return (...rest) => {
        if (timer) {
          clearTimeout(timer);
          timer = null;
        }
        timer = setTimeout(() => {
          fn(...rest);
          clearTimeout(timer);
          timer=null;
        }, ms);
      };
    };
    

    2.节流

    节流函数也是应用于短时间内连续多次调用同一个函数场景。常见用于页面滚动时,不断拉取数据,也是不能每次触发滚动都去请求接口,我们同样做一个延迟函数,每次触发只要前一个延迟函数还在就不做任何操作,等到前一个延迟函数执行完,我们就再创建一个延迟函数,达到的效果就是每隔一定间隔去拉取数据,也是可以大大降低网络开销。

    const throttle = (fn, ms = 500) => {
      let timer = null;
      return (...rest) => {
        if (!timer) {
          timer = setTimeout(() => {
            fn(...rest);
            clearTimeout(timer);
            timer = null;
          }, ms);
        }
      };
    }
    
  • 你可能感兴趣的:(javaScript中的闭包以及闭包的经典案例)