js css html

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

2022-01-21  本文已影响0人  Promi5e

前言

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

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

const list = document.querySelector('li');
for (var i = 0; i < list.length - 1; i++) {
  list[i].onclick = function(i) {
    console.log(i);
  }
}
// 为了方便执行,用seTimeout函数代替给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

这样写的问题就是,无论你点击哪个<li>标签,总是打印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);
    }
  };
}
上一篇下一篇

猜你喜欢

热点阅读