JavaScript:闭包、防抖与节流

一,闭包

1,什么是闭包

闭包是指一个函数和其周围的词法环境(lexical environment)的组合。
换句话说,闭包允许一个函数访问并操作函数外部的变量。

闭包的核心特性:

  1. 函数内部可以访问外部函数的变量
  2. 即使外部函数已经返回,内部函数仍然可以访问这些变量
  3. 闭包可以保护变量不被垃圾回收机制回收

一个简单的例子:

function outerFunction(x) {let y = 10function innerFunction() {// 内部函数innerFunction可以访问外部函数outerFunction的变量yconsole.log(x + y)}// 外部函数outerFunction返回内部函数innerFunctionreturn innerFunction
}const closure = outerFunction(5)closure()   // 输出: 15
// 即使outerFunction已经执行完毕,但是closure仍然可以访问 x 和 y 的值
closure()   // 输出: 15

在这个例子中:

  • outerFunction 返回了 innerFunction
  • innerFunction 形成了一个闭包,它可以访问 outerFunction 的参数 x 和局部变量 y
  • 即使 outerFunction 已经执行完毕,closure 仍然可以访问 x 和 y

2,闭包的应用场景

1,数据隐私:闭包可以用来创建私有变量和方法

function createCounter() {let count = 0;return {increment: function() { count++; },getCount: function() { return count; }};
}const counter = createCounter();
counter.increment();
console.log(counter.getCount()); // 输出: 1
console.log(counter.count); // 输出: undefined

2,函数工厂:闭包可以用来创建定制的函数

function multiplyBy(factor) {return function(number) {return number * factor;};
}const double = multiplyBy(2);
console.log(double(5)); // 输出: 10

3,模块化代码:闭包可以用于实现模块模式,封装私有状态和行为

const module = (function() {let privateVariable = 0;function privateFunction() {console.log('私有函数');}return {publicMethod: function() {privateVariable++;privateFunction();},getPrivateVariable: function() {return privateVariable;}};
})();module.publicMethod();
console.log(module.getPrivateVariable()); // 输出: 1

3,注意事项

1,内存管理:闭包会保持对其外部作用域的引用,这可能导致内存泄漏。

// 潜在的内存泄漏
function createLargeArray() {let largeArray = new Array(1000000).fill('some data');return function() {console.log(largeArray[0]);};
}let printArrayItem = createLargeArray(); // largeArray 会一直存在于内存中// 解决方式1:在不需要时解除引用
printArrayItem = null; // 现在 largeArray 可以被垃圾回收// 解决方式2:立即执行函数表达式(IIFE)来限制闭包的生命周期
(function() {let largeArray = new Array(1000000).fill('some data');console.log(largeArray[0]);
})(); // largeArray 在函数执行后立即可以被回收

2,循环中创建闭包:闭包会捕获循环变量的最终值,而不是每次迭代的值。

// 闭包会捕获循环变量的最终值,而不是每次迭代的值
for (var i = 1; i <= 5; i++) {setTimeout(function() {console.log(i);}, i * 1000);
}// 解决方法:
// 1. 使用 let 替换 var
// 2:使用立即执行函数创建新的作用域
for (var i = 1; i <= 5; i++) {(function(j) {setTimeout(function() {console.log(j);}, j * 1000);})(i);
}

3,性能考虑:过度使用可能会影响性能,每个闭包都会占用内存,并且可能影响垃圾回收。在性能敏感的应用中应谨慎使用闭包。

4,this 绑定问题:在闭包中,this 的值可能会出人意料。

const obj = {value: 'Hello',sayHello: function() {setTimeout(function() {console.log(this.value);}, 1000);}
};obj.sayHello(); // 输出: undefined

sayHello 方法内部的 setTimeout 使用了一个普通的函数( function() { … } ),而不是箭头函数。普通函数的 this 关键字在运行时是根据调用上下文来确定的,而不是根据定义时的上下文。
在 setTimeout 的回调函数中, this 不再指向 obj ,而是指向全局对象(在浏览器中是 window ),或者在严格模式下是 undefined 。因此,当你尝试访问 this.value 时,它实际上是在访问全局对象的 value 属性,而全局对象并没有 value 属性,所以输出为 undefined 。
只需要改用箭头函数,因为箭头函数不会创建自己的 this ,它会捕获外部上下文的 this 值:

const obj = {value: 'Hello',sayHello: function() {setTimeout(() => {console.log(this.value);}, 1000);}
};
obj.sayHello(); // 输出: Hello

二,防抖

防抖的核心思想是,在事件被触发n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时。这可以使得连续的函数调用变为一次。

在这里插入图片描述

举个例子:

function debounce(func, delay) {let timer = null;return function(...args) {clearTimeout(timer);timer = setTimeout(() => {func.apply(this, args);}, delay);}
}// Usage
const expensiveOperation = debounce(() => {console.log('Expensive operation')
}, 500)expensiveOperation()
  • 在 debounce 函数中, timer 变量是在 debounce 函数的作用域内定义的。返回的函数(即 function (…args) )可以访问 timer 变量,因此每次调用返回的函数时,它都可以使用和修改 timer ,从而实现防抖的效果。

在搜索框输入查询、表单验证、按钮提交事件、浏览器窗口缩放resize事件中的应用:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Debounce Demo</title><style>body {font-family: Arial, sans-serif;max-width: 600px;margin: 0 auto;padding: 20px;}.section {margin-bottom: 20px;padding: 10px;border: 1px solid #ddd;border-radius: 5px;}input, button {margin: 5px 0;padding: 5px;}#email-error {color: red;}</style>
</head>
<body><h1>Debounce Demo</h1><div class="section"><h2>1. Search Input</h2><input type="text" id="search-input" placeholder="Search..."><div id="search-results"></div></div><div class="section"><h2>2. Email Validation</h2><input type="email" id="email-input" placeholder="Enter email"><div id="email-error"></div></div><div class="section"><h2>3. Submit Button</h2><button id="submit-button">Submit</button><div id="submit-result"></div></div><div class="section"><h2>4. Window Resize</h2><div id="window-size"></div></div><script>// Debounce functionfunction debounce(func, delay) {let timer = null;return function(...args) {clearTimeout(timer);timer = setTimeout(() => {func.apply(this, args);}, delay);}}// 1. Search Inputconst searchInput = document.getElementById('search-input');const searchResults = document.getElementById('search-results');const debouncedSearch = debounce(function(query) {console.log(`Searching for: ${query}`);searchResults.textContent = `Results for: ${query}`;}, 300);searchInput.addEventListener('input', function(e) {debouncedSearch(e.target.value);});// 2. Email Validationconst emailInput = document.getElementById('email-input');const emailError = document.getElementById('email-error');const debouncedValidateEmail = debounce(function(email) {const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);emailError.textContent = isValid ? '' : 'Invalid email format';}, 500);emailInput.addEventListener('input', function(e) {debouncedValidateEmail(e.target.value);});// 3. Submit Buttonconst submitButton = document.getElementById('submit-button');const submitResult = document.getElementById('submit-result');const debouncedSubmit = debounce(function() {console.log('Form submitted');submitResult.textContent = 'Form submitted at ' + new Date().toLocaleTimeString();}, 1000);submitButton.addEventListener('click', debouncedSubmit);// 4. Window Resizeconst windowSize = document.getElementById('window-size');const debouncedResize = debounce(function() {const size = `${window.innerWidth}x${window.innerHeight}`;console.log(`Window resized to: ${size}`);windowSize.textContent = `Window size: ${size}`;}, 250);window.addEventListener('resize', debouncedResize);// Initial call to set the initial window sizedebouncedResize();</script>
</body>
</html>

三,节流

节流的核心思想是,在一个单位时间内,只能触发一次函数。如果在单位时间内触发多次函数,只有一次生效。

在这里插入图片描述

防抖和节流的主要区别:

  • 防抖是在最后一次事件触发后才执行函数,而节流是在一定时间内只执行一次。
  • 防抖适合用于用户输入验证等需要等待用户操作完成的场景,而节流适合用于限制持续触发事件的频率。

举个例子:

function throttle(func, delay) {let lastTime = 0;return function(...args) {const now = Date.now();if (now - lastTime >= delay) {func.apply(this, args);lastTime = now;}}
}// Usage
const expensiveOperation = throttle(() => {console.log('Expensive operation')
}, 1000)expensiveOperation()

在滚动加载更多、按钮点击事件、DOM元素拖拽、Canvas画笔中的应用:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Throttle Demo</title><style>body {font-family: Arial, sans-serif;max-width: 800px;margin: 0 auto;padding: 20px;}.section {margin-bottom: 20px;padding: 10px;border: 1px solid #ddd;border-radius: 5px;}#scroll-container {height: 200px;overflow-y: scroll;border: 1px solid #ccc;padding: 10px;}#drag-container {width: 300px;height: 100px;background-color: #f0f0f0;position: relative;}#draggable {width: 50px;height: 50px;background-color: #3498db;position: absolute;cursor: move;}#canvas {border: 1px solid #000;}</style>
</head>
<body><h1>Throttle Demo</h1><div class="section"><h2>1. Scroll Loading</h2><div id="scroll-container"><div id="scroll-content"></div></div></div><div class="section"><h2>2. Button Click</h2><button id="click-button">Click Me Rapidly</button><div id="click-result"></div></div><div class="section"><h2>3. Drag Element</h2><div id="drag-container"><div id="draggable"></div></div><div id="drag-result"></div></div><div class="section"><h2>4. Canvas Drawing</h2><canvas id="canvas" width="300" height="200"></canvas></div><script>// Throttle functionfunction throttle(func, limit) {let inThrottle;return function(...args) {if (!inThrottle) {func.apply(this, args);inThrottle = true;setTimeout(() => inThrottle = false, limit);}}}// 1. Scroll Loadingconst scrollContainer = document.getElementById('scroll-container');const scrollContent = document.getElementById('scroll-content');let itemCount = 20;function addItems(count) {for (let i = 0; i < count; i++) {const item = document.createElement('p');item.textContent = `Item ${itemCount++}`;scrollContent.appendChild(item);}}const throttledScroll = throttle(function() {if (scrollContainer.scrollTop + scrollContainer.clientHeight >= scrollContainer.scrollHeight - 50) {console.log('Loading more items...');addItems(10);}}, 500);scrollContainer.addEventListener('scroll', throttledScroll);addItems(20); // Initial items// 2. Button Clickconst clickButton = document.getElementById('click-button');const clickResult = document.getElementById('click-result');let clickCount = 0;const throttledClick = throttle(function() {clickCount++;clickResult.textContent = `Button clicked ${clickCount} times`;}, 1000);clickButton.addEventListener('click', throttledClick);// 3. Drag Elementconst draggable = document.getElementById('draggable');const dragResult = document.getElementById('drag-result');let isDragging = false;draggable.addEventListener('mousedown', () => isDragging = true);document.addEventListener('mouseup', () => isDragging = false);const throttledDrag = throttle(function(e) {if (isDragging) {const containerRect = draggable.parentElement.getBoundingClientRect();let x = e.clientX - containerRect.left - draggable.offsetWidth / 2;let y = e.clientY - containerRect.top - draggable.offsetHeight / 2;x = Math.max(0, Math.min(x, containerRect.width - draggable.offsetWidth));y = Math.max(0, Math.min(y, containerRect.height - draggable.offsetHeight));draggable.style.left = `${x}px`;draggable.style.top = `${y}px`;dragResult.textContent = `Position: (${x.toFixed(0)}, ${y.toFixed(0)})`;}}, 50);document.addEventListener('mousemove', throttledDrag);// 4. Canvas Drawingconst canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');let isDrawing = false;let lastX = 0;let lastY = 0;canvas.addEventListener('mousedown', (e) => {isDrawing = true;[lastX, lastY] = [e.offsetX, e.offsetY];});canvas.addEventListener('mouseup', () => isDrawing = false);canvas.addEventListener('mouseout', () => isDrawing = false);const throttledDraw = throttle(function(e) {if (!isDrawing) return;ctx.beginPath();ctx.moveTo(lastX, lastY);ctx.lineTo(e.offsetX, e.offsetY);ctx.stroke();[lastX, lastY] = [e.offsetX, e.offsetY];}, 20);canvas.addEventListener('mousemove', throttledDraw);</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57040.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一款新开源跨平台的.NET Word(docx)模版导出引擎,完美支持Linux和Mac操作系统(附源码)

前言 在数字化办公日益盛行的今天&#xff0c;文档处理成为了我们日常工作不可或缺的一部分。然而&#xff0c;许多传统的文档处理工具都依赖于特定的操作系统和复杂的组件安装&#xff0c;这无疑给跨平台办公带来了诸多不便。为了解决这一问题&#xff0c;我们找到了一个新的…

【MR开发】在Pico设备上接入MRTK3(一)——在Unity工程中导入MRTK3依赖

写在前面的话 在Pico上接入MRTK3&#xff0c;目前已有大佬开源。 https://github.com/Phantomxm2021/PicoMRTK3 也有值得推荐的文章。 MRTK3在PICO4上的使用小结 但由于在MacOS上使用MRTK3&#xff0c;无法通过Mixed Reality Feature Tool工具管理MRTK3安装包。 故记录一下…

◇【论文_20151120_20160405v3】Dueling Network 决斗〔Google DeepMind〕

整理代码&#xff1a;Dueling_DQN__Pendulum_v1.ipynb https://arxiv.org/abs/1511.06581 Dueling Network Architectures for Deep Reinforcement Learning 文章目录 摘要1. 引言1.1. 相关工作 2. 背景2.1. Deep Q-networks 【DQN】2.2. Double Deep Q-networks 【DDQN】2.3…

OpenCV高级图形用户界面(13)选择图像中的一个矩形区域的函数selectROI()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 允许用户在给定的图像上选择一个感兴趣区域&#xff08;ROI&#xff09;。 该功能创建一个窗口&#xff0c;并允许用户使用鼠标来选择一个 ROI。…

其他css的用途

1.animation-fill-mode: backwards; //避免了在动画开始前元素的突然显现&#xff0c;动画必要。 2.用rem响应式字体大小&#xff0c;可以在html样式定义font-size?(例10px&#xff0c;62.5%(100%是16px))。然后样式就可以用rem代替px。 3.color: transparent;: 这行代码将文…

计算生物学与生物信息学漫谈-2-测序深度/读长质量和Fasta处理

上一篇文章中我们介绍了测序技术的由来与发展&#xff0c;那么在介绍第三代测序的时候&#xff0c;我们提到了关于测序深度和读长的问题&#xff0c;那么本篇文章就详解介绍一下。 计算生物学与生物信息学漫谈-1-测序一路走来-CSDN博客 目录 1.测序深度SEQUENCING DEPTH &…

《AI生成式工具使用》之:自助生成视频

目录 背景说明及目标&#xff1a; 实现过程&#xff1a; 1、有问题找度娘 2、利用剪映AI生成视频具体步骤 剪映AI感受 3、利用万彩AI生成视频具体步骤 万彩AI感受 4、利用腾讯云剪生成视频具体步骤 腾讯云剪感受 最终结论&#xff1a; 关注我&#xff0c;躺不平就一起…

【部署篇】RabbitMq-02单机模式部署

RabbitMQ和Erlang/OTP兼容性矩阵 下表提供了当前支持的RabbitMQ版本系列的Erlang兼容性矩阵。更多RabbitMQ版本&#xff0c;请参阅官网的系列兼容性列表。官网地址&#xff1a;https://www.rabbitmq.com/docs/which-erlang RabbitMQ版本最小支持版本最大支持版本备注 4.0.24.…

Axure重要元件三——中继器添加数据

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 本节课&#xff1a;中继器添加数据 课程内容&#xff1a;添加数据项、自动添加序号、自动添加数据汇总 应用场景&#xff1a;表单数据的添加 案例展示&#xff1a; 步骤…

经验是最坏的老师

奥斯卡.王尔德说过&#xff1a;经验是最坏的老师。他经常先考试&#xff0c;然后再给出指导。 这让我想起了另外一句话&#xff1a;愚笨的人&#xff0c;往往都在犯同样的错误&#xff1b;普通的人&#xff0c;从自己的错误中学习&#xff1b;聪明人从别人的错误中学习。 如果…

Linux 防火墙的开启、关闭、禁用命令

Linux 防火墙的开启、关闭、禁用命令 文章目录 Linux 防火墙的开启、关闭、禁用命令1.设置开机启用防火墙2.设置开机禁用防火墙3.启动防火墙4.关闭防火墙5.检查防火墙状态 1.设置开机启用防火墙 systemctl enable firewalld.service2.设置开机禁用防火墙 systemctl disable f…

006、链表分割

0、题目描述 链表分割 这道题的思路&#xff0c;遍历原链表&#xff0c;小于x的放到一个链表里&#xff0c;大于x的放到另一个链表里。然后把两个链表接起来。 建立的两个新链表都是有哨兵位的&#xff0c;也就是有头结点&#xff0c;排序结束后要free两个头结点。 1、法1 还…

CSS3 提示框带边角popover

CSS3 提示框带边角popover。因为需要绝对定位子元素&#xff08;这里就是伪元素&#xff09;&#xff0c;所以需要将其设置为相对对位 <!DOCTYPE html> <html> <head> <title>test1.html</title> <meta name"keywords" con…

格点拉格朗日插值与PME算法

技术背景 在前面的一篇博客中&#xff0c;我们介绍了拉格朗日插值法的基本由来和表示形式。这里我们要介绍一种拉格朗日插值法的应用场景&#xff1a;格点拉格朗日插值法。这种场景的优势在于&#xff0c;如果我们要对整个实数空间进行求和或者积分&#xff0c;计算量是随着变量…

JDK中socket源码解析

目录 1、Java.net包 1. Socket通信相关类 2. URL和URI处理类 3. 网络地址和主机名解析类 4. 代理和认证相关类 5. 网络缓存和Cookie管理类 6. 其他网络相关工具类 2、什么是socket&#xff1f; 3、JDK中socket核心Api 4、核心源码 1、核心方法 2、本地方法 3、lin…

SQL Server 2019数据库“正常,已自动关闭”

现象&#xff1a; SQL Server 2019中&#xff0c;某个数据库在SQL Server Management Studio&#xff08;SSMS&#xff09;中的状态显示为“正常&#xff0c;已自动关闭”。 解释&#xff1a; 如此显示&#xff0c;是由于该数据库的AUTO_ CLOSE选项被设为True。 在微软的官…

基于 Konva 实现Web PPT 编辑器(三)

完善公式 上一节我们简单讲述了公式的使用&#xff0c;并没有给出完整的样例&#xff0c;下面还是完善下相关步骤&#xff0c;我们是默认支持公式的编辑功能的哈&#xff0c;因此&#xff0c;我们只需要提供必要的符号即可&#xff1a; 符号所表达的含义是 mathlive 的command命…

电力系统IEC-101报文主要常用详解

文章目录 1️⃣ IEC-1011.1 前言1.2 101规约简述1.3 固定帧格式1.4 可变帧格式1.5 ASDU1.5.1 常见类型标识1.5.2 常见结构限定词1.5.3 常见传送原因1.5.4 信息体地址 1.6 常用功能报文1.6.1 初始化链路报文1.6.2 总召报文1.6.3 复位进程1.8.4 对时1.8.4.1时钟读取1.8.4.2时钟写…

适用于 vue react Es6 jQuery 等等的组织架构图(组织结构图)

我这里找的是 OrgChart 插件; 地址: GitHub - dabeng/OrgChart: Its a simple and direct organization chart plugin. Anytime you want a tree-like chart, you can turn to OrgChart. 这里面能满足你对组织架构图的一切需求! ! ! 例: 按需加载 / 拖拽 / 编辑 / 自定义 / …

基于STM32F407VGT6芯片----跑马灯实验

一、在STM32F407VGT6芯片中配置GPIO环境 对于一个跑马灯实验&#xff0c;首先&#xff0c;要了解的就是&#xff0c;芯片是如何构造出来的&#xff0c;设计GPIO引脚&#xff1a;根据原理图&#xff0c; PC4&#xff0c;PC5,PC6,PC7 为 LED 输出控制管脚&#xff0c;PE0 为蜂鸣…