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.rhkb.cn/news/452547.html

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

相关文章

ApacheShiro反序列化 550 721漏洞

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理个漏洞被称为 Shiro550 是因为在Apache Shiro的GitHub问题跟踪器中&#xff0c;该漏洞最初被标记为第550个问题,721漏洞名称也是由此而来 Shiro-550 CVE-2016-4437 Shiro反序列化Docker复现 …

Pytest参数详解 — 基于命令行模式!

1、--collect-only 查看在给定的配置下哪些测试用例会被执行 2、-k 使用表达式来指定希望运行的测试用例。如果测试名是唯一的或者多个测试名的前缀或者后缀相同&#xff0c;可以使用表达式来快速定位&#xff0c;例如&#xff1a; 命令行-k参数.png 3、-m 标记&#xff08;…

在wsl2下将Ubuntu从一个盘移动到其他盘

参考文章&#xff1a; wsl下将Ubuntu从c盘移动到其他盘 WSL数据迁移(迁移ext4.vhdx) WSL 系统迁移&#xff08;2&#xff09;&#xff0c;导入虚拟机磁盘映像 .vhdx ext4/fs WSL2迁移后默认登陆用户为root的解决方案 操作过程&#xff1a; 1.查看当前系统中wsl分发版本 …

系统托盘图标+快捷启动(Python)

QkStart 我把这个程序命名为QkStart 代码 # -*- coding: utf-8 -*- # Environment PyCharm # File_name QkStart |User Pfolg # 2024/10/19 22:06 import threading import time import pystray from PIL import Image from pystray import MenuItem, Menu import o…

【网络安全】缓存欺骗问题之查看个人资料接口

未经许可,不得转载。 文章目录 正文正文 目标网站 target.com,查看个人资料页面时,API 端点为/get_user,完整的 URL 是 https://target.com/web-api/v1/get_user?timestamp=123456(其中 timestamp 是一个易受攻击的参数)。 我注意到响应中有一个 cf-cache-status= MISS…

【ESP32-IDFV5.3.1开发】带SSL的MQTT-demo连接教程

目录 1.VSCODE以及IDF环境配置(略) 2.准备demo 2.1打开VSCODE&#xff0c;主菜单创建示例 找到SSL对应demo&#xff0c;点击创建&#xff0c;并成功创建项目&#xff0c;点击编译&#xff0c;显示编译成功即可以下一步。 确认该demo支持的开发板是你手上的开发板 3.修改demo配…

了解EasyNVR及EasyNVS,EasyNVR连接EasyNVS显示授权超时如何解决?什么原因?

我们先来了解NVR批量管理软件/平台EasyNVR&#xff0c;它深耕市场多年&#xff0c;为用户提供多种协议&#xff0c;兼容多种厂商设备&#xff0c;包括但不限于支持海康&#xff0c;大华&#xff0c;宇视&#xff0c;萤石&#xff0c;天地伟业&#xff0c;华为设备。 NVR录像机…

基于模型设计的智能平衡移动机器人-基础实验SPI

目录 SPI介绍 模型搭建 SPI介绍 SPI即Serial Peripheral Interface是高速同步串行输入/输出端口。SPI目前被广泛用于外部移位寄存器、D/A、A/D、串行EEPROM、LED显示驱动器等外部芯片的扩展。与前文介绍的SCI最大的区别是,SPI是同步串行接口。 SPI接口的通信原理简单&#…

(五)若使用LQR控制小车倒立摆,该如何对小车和摆杆的动力学方程线性化?哪些变量是可以进行简化的,线性化后的状态空间方程应该怎么列写

写在前面&#xff1a; 关于lqr控制的讲解&#xff0c;可以观看如下三个视频&#xff1a; 2. LQR数学公式理解_哔哩哔哩_bilibili 如何感性地理解LQR控制&#xff1f;_哔哩哔哩_bilibili LQR简介与使用_哔哩哔哩_bilibili 正文&#xff1a; 在之前系列的文章中我们已经得出…

WGCLOUD使用手册 - 文件防篡改监测

WGCLOUD作为一款运维平台&#xff0c;天然具备了文件防篡改监测能力&#xff0c;也可以监测文件夹 如果发现文件或者文件夹下的文件&#xff0c;被修改&#xff0c;被删除&#xff0c;被添加等操作&#xff0c;会立刻发送告警通知 如果设置了自动恢复指令&#xff0c;也会立刻…

极速体验:实用的前端性能优化技巧

本文将深入探讨一系列实用的前端性能优化方案&#xff0c;从基础知识到高级技巧&#xff0c;我们将揭示如何让你的网站在瞬息万变的互联网中脱颖而出&#xff0c;无论你是经验丰富的开发者还是刚入行的新手&#xff0c;这篇文章都将为你提供宝贵的见解和实践建议。 目录 &…

基于SSM+微信小程序的打印室预约管理系统(打印2)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的打印室预约管理系统实现了管理员和用户两个角色。 1、管理员功能有个人中心&#xff0c;用户管理&#xff0c;附近打印店管理&#xff0c;文件打印管理&#xff0c;当…

rk3568 android11 单独烧写内核。

问题: 我现在 遇到一个问题,如果我单独 烧写boot.img 的话,就会进入 recovery 的模式。 如下图: 问题说明: 如果我烧写的 Update.img 是可以启动的。那么我再烧写一个 编译 update.img 顺带编译出来的 boot.img 是可以正常启动的。 问题出在 , 如果我 重新编译一遍 ,使…

LVGL-从入门到熟练使用

LVGL简介 LVGL&#xff08; Light and Versatile Graphics Library &#xff09;是一个轻量、多功能的开源图形库。 1、丰富且强大的模块化图形组件&#xff1a;按钮 、图表 、列表、滑动条、图片等 2、高级的图形引擎&#xff1a;动画、抗锯齿、透明度、平滑滚动、图层混合等…

WebGL编程指南 - WebGL入门

初识绘图流程、缓冲区、着色器、attribute和uniform变量 先画一个蓝色的正方形 html代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content&…

stm32 为什么有2个晶振?8mhz+32.768k

1个是系统时钟晶振是单片机内部系统的主时钟源&#xff0c;它负责控制整个系统的时钟频率。这个晶振的频率一般比较高&#xff0c;通常在几十MHz到几百MHz不等。它和CPU以及各种总线之间相互配合&#xff0c;从而协同工作。 另外一个是外设时钟晶振则通常用于单片机的内部外设…

【大模型问答测试】大模型问答测试脚本实现(第二版)——接入pytest与代码解耦

背景 接上一篇&#xff0c;【大模型问答测试】大模型问答测试脚本实现&#xff08;第一版&#xff09;。 在实现自动化的时候&#xff0c;原先把很多方法与request请求写在一块了&#xff0c;趁着目前实现接口数量较少&#xff0c;决定对代码进行解耦&#xff0c;并且清晰目录…

大数据-172 Elasticsearch 索引操作 与 IK 分词器 自定义停用词 Nginx 服务

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

[论文阅读]RGB-Depth Fusion GAN for Indoor Depth Completion

摘要 由于固有的限制&#xff0c;如无法感知透明物体和有限的距离范围&#xff0c;室内深度传感器捕获的原始深度图像通常会有大面积的深度值缺失。这种不完整的深度图给许多后续视觉任务带来了负担&#xff0c;尽管提出了很多深度补全方法来缓解这一问题。但是现有的大多数方…

解决k8s集群中安装ks3.4.1开启日志失败问题

问题 安装kubesphere v3.4.1时&#xff0c;开启了日志功能&#xff0c;部署时有三个pod报错了 Failed to pull image “busybox:latest”: rpc error: code Unknown desc failed to pull and unpack image “docker.io/library/busybox:latest”: failed to copy: httpRead…