闭包在函数防抖(Debouncing)与节流(Throttling)中的实现

各位观众,各位听众,晚上好!欢迎来到今天的“闭包奇妙夜”!我是你们今晚的导游,将带领大家探索闭包这个编程世界里既神秘又实用的概念,并揭开它在函数防抖和节流这两种常见性能优化技巧中的应用。

准备好了吗?让我们系好安全带,开启这段代码之旅吧!🚀

第一幕:闭包,你是我的小呀小苹果🍎

首先,我们来聊聊闭包。闭包这玩意儿,就像一个装着秘密的小盒子,或者更像你小时候藏在床底下的零食,只有你知道,而且还能随时拿出来吃!

更专业一点儿说,闭包是指有权访问另一个函数作用域中的变量的函数。这听起来有点绕口,让我们用一个简单的例子来说明:

function outerFunction() {
  let outerVar = "我是外面的变量";

  function innerFunction() {
    console.log(outerVar); // innerFunction 可以访问 outerVar
  }

  return innerFunction;
}

let myClosure = outerFunction();
myClosure(); // 输出 "我是外面的变量"

在这个例子中,innerFunction 就是一个闭包。即使 outerFunction 已经执行完毕,innerFunction 仍然可以访问 outerFunction 作用域中的 outerVar 变量。

为什么会这样呢?因为当 outerFunction 返回 innerFunction 时,innerFunction 就“记住”了 outerFunction 的作用域,并将这个作用域链保存了下来。即使 outerFunction 已经执行完毕,这个作用域链仍然存在,innerFunction 仍然可以通过这个作用域链访问到 outerVar

闭包就像一个“记忆大师”,它记住了创建它的环境,并将这个环境一起带走。 这就是闭包的核心所在。

第二幕:防抖,别急,让我喘口气💨

现在,我们进入正题,聊聊函数防抖(Debouncing)。想象一下,你正在做一个搜索功能,用户每输入一个字,就向服务器发送一次请求。如果用户输入速度很快,那就会产生大量的请求,给服务器带来很大的压力。

这时候,防抖就派上用场了!防抖就像一个“贤内助”,它会告诉你:“别急,等用户停止输入一段时间后再发送请求。”

防抖的原理是:在一定时间内,如果事件被触发多次,则只有最后一次会执行。

让我们用一个例子来说明:

function debounce(func, delay) {
  let timeoutId;

  return function(...args) {
    clearTimeout(timeoutId); // 清除之前的定时器

    timeoutId = setTimeout(() => {
      func.apply(this, args); // 执行函数
    }, delay);
  };
}

function search(query) {
  console.log(`Searching for: ${query}`);
}

const debouncedSearch = debounce(search, 300); // 300ms 的防抖时间

// 模拟用户输入
debouncedSearch("a");
debouncedSearch("ab");
debouncedSearch("abc");
setTimeout(() => debouncedSearch("abcd"), 400); // 最终只执行这一个

在这个例子中,debounce 函数返回了一个新的函数,这个新函数就是防抖后的函数。每次调用 debouncedSearch 函数时,都会清除之前的定时器,并重新设置一个新的定时器。只有当用户停止输入一段时间后,定时器才会触发,执行 search 函数。

注意这里,debounce 函数返回的匿名函数就构成了一个闭包。 它“记住”了 timeoutIdfunc 这两个变量。

函数 作用
debounce 创建一个防抖函数,接收一个函数和一个延迟时间作为参数。
匿名函数 返回的防抖后的函数,形成闭包,可以访问 debounce 函数作用域中的 timeoutIdfunc 变量。
setTimeout 设置一个定时器,在延迟时间后执行 func 函数。
clearTimeout 清除之前的定时器,防止函数被多次执行。

防抖适用于:

  • 搜索框输入
  • 调整浏览器窗口大小
  • 滚动事件

第三幕:节流,雨露均沾,一个都不能少 💧

接下来,我们来聊聊函数节流(Throttling)。节流就像一个“公平的分配者”,它会告诉你:“不能让事件太频繁地执行,要雨露均沾,每个一段时间执行一次。”

节流的原理是:在一定时间内,只能执行一次。

让我们用一个例子来说明:

function throttle(func, delay) {
  let lastTime = 0;

  return function(...args) {
    const now = Date.now();

    if (now - lastTime >= delay) {
      func.apply(this, args);
      lastTime = now;
    }
  };
}

function scrollHandler() {
  console.log("Scrolling...");
}

const throttledScrollHandler = throttle(scrollHandler, 200); // 200ms 的节流时间

window.addEventListener("scroll", throttledScrollHandler);

在这个例子中,throttle 函数返回了一个新的函数,这个新函数就是节流后的函数。每次调用 throttledScrollHandler 函数时,都会判断当前时间距离上次执行的时间是否超过了 delay。如果超过了,则执行 scrollHandler 函数,并更新 lastTime

同样, throttle 函数返回的匿名函数也构成了一个闭包。它“记住”了 lastTimefunc 这两个变量。

函数 作用
throttle 创建一个节流函数,接收一个函数和一个延迟时间作为参数。
匿名函数 返回的节流后的函数,形成闭包,可以访问 throttle 函数作用域中的 lastTimefunc 变量。
Date.now 获取当前时间戳。
if条件判断 判断当前时间距离上次执行的时间是否超过了 delay

节流适用于:

  • 滚动事件
  • 鼠标移动事件
  • 按钮点击事件

第四幕:闭包,防抖,节流,三剑客的完美配合 🤝

现在,我们来总结一下闭包在防抖和节流中的作用。

  • 闭包是实现防抖和节流的关键。 防抖和节流都需要“记住”一些状态,比如定时器 ID、上次执行的时间等。这些状态需要保存在函数的作用域中,而闭包正好可以实现这个功能。
  • 闭包可以防止变量被意外修改。 防抖和节流都需要在函数内部修改一些变量,比如定时器 ID、上次执行的时间等。如果这些变量暴露在全局作用域中,可能会被意外修改,导致防抖和节流失效。而闭包可以将这些变量封装在函数的作用域中,防止被意外修改。

我们可以用一个表格来总结一下防抖和节流的区别:

特性 防抖 (Debouncing) 节流 (Throttling)
触发时机 在一段时间内,只执行最后一次。 在一段时间内,最多执行一次。
目的 减少不必要的请求,优化性能。 控制事件的触发频率,防止页面卡顿。
适用场景 搜索框输入、调整浏览器窗口大小、滚动事件。 滚动事件、鼠标移动事件、按钮点击事件。
状态 需要记住定时器 ID。 需要记住上次执行的时间。
闭包作用 保存定时器 ID,防止被意外修改。 保存上次执行的时间,防止被意外修改。
形象比喻 “贤内助”,告诉你:“别急,等用户停止输入一段时间后再发送请求。” “公平的分配者”,告诉你:“不能让事件太频繁地执行,要雨露均沾,每个一段时间执行一次。”

第五幕:代码优化,让你的代码飞起来 🚀

最后,我们来聊聊如何优化防抖和节流的代码。

  • 使用 requestAnimationFrame 代替 setTimeout requestAnimationFrame 可以更好地与浏览器的渲染周期同步,从而提高性能。
  • 使用 leadingtrailing 选项来控制函数的执行时机。 leading 选项表示在延迟开始前是否立即执行一次函数,trailing 选项表示在延迟结束后是否执行最后一次函数。
  • 使用第三方库,例如 Lodash 和 Underscore.js。 这些库提供了现成的防抖和节流函数,可以直接使用。

一个使用 leadingtrailing 选项的防抖函数:

function debounce(func, delay, options = { leading: false, trailing: true }) {
  let timeoutId;
  let lastArgs;
  let lastThis;

  const { leading, trailing } = options;

  function invokeFunc(time) {
    timeoutId = null;
    if (lastArgs) {
      func.apply(lastThis, lastArgs);
      lastArgs = lastThis = null;
    }
  }

  return function(...args) {
    lastArgs = args;
    lastThis = this;

    if (!timeoutId) {
      if (leading) {
        func.apply(this, args);
      }
      timeoutId = setTimeout(invokeFunc, delay);
    } else if (trailing) {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(invokeFunc, delay);
    }
  };
}

总结:

今天,我们一起探索了闭包的奥秘,并了解了它在函数防抖和节流中的应用。希望通过今天的学习,你能够更好地理解闭包,并能够灵活运用防抖和节流来优化你的代码。

记住,闭包就像一个装着秘密的小盒子,防抖就像一个“贤内助”,节流就像一个“公平的分配者”。 掌握它们,你就能在编程的世界里游刃有余! 💪

谢谢大家! 晚安! 🌙

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注