JavaScript:闭包、防抖与节流

一,闭包

1,什么是闭包

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

闭包的核心特性:

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

一个简单的例子:

function outerFunction(x) {
    let y = 10

    function innerFunction() {
        // 内部函数innerFunction可以访问外部函数outerFunction的变量y
        console.log(x + y)
    }

    // 外部函数outerFunction返回内部函数innerFunction
    return 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 function
        function debounce(func, delay) {
            let timer = null;
            return function(...args) {
                clearTimeout(timer);
                timer = setTimeout(() => {
                    func.apply(this, args);
                }, delay);
            }
        }

        // 1. Search Input
        const 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 Validation
        const 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 Button
        const 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 Resize
        const 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 size
        debouncedResize();
    </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 function
        function throttle(func, limit) {
            let inThrottle;
            return function(...args) {
                if (!inThrottle) {
                    func.apply(this, args);
                    inThrottle = true;
                    setTimeout(() => inThrottle = false, limit);
                }
            }
        }

        // 1. Scroll Loading
        const 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 Click
        const 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 Element
        const 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 Drawing
        const 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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(AtCoder Beginner Contest 375)B - Traveling Takahashi Problem

&#xff08;AtCoder Beginner Contest 375&#xff09;B - Traveling Takahashi Problem 题目大意 按顺序给定n个点 ( x i , y i ) (x_i,y_i) (xi​,yi​) 求按顺序走过这n个点并回到原点的总距离 任意两点之间的距离是欧几里得距离 思路 按照题意模拟即可&#xff0c;时间…

Cisco软件基础使用

‘地址还未设置’在交换机的CIL中输入enable进入特权模式&#xff0c;输入config t 进入设置 设置进入特权模式的密码和登录的密码 为交换机设置IP地址 未设置地址前显示如下。 下图设置进入特权模式的密码123456 &#xff0c;远程访问登录密码cisco。 exit退一步进入interfa…

cefsharp63.0.3(Chromium 63.0.3239.132)支持H264视频播放-PDF预览 老版本回顾系列体验

一、版本 版本:Cef 63/CefSharp63.0.3/Chromium63.0.3239.132/支持H264/支持PDF预览 支持PDF预览和H264推荐版本 63/79/84/88/100/111/125 <

免费字体二次贩卖;刮刮乐模拟器;小报童 | 生活周刊 #4

Raycast 的两款在线工具 Raycast 公司出品&#xff0c;必属精品&#xff0c;之前的代码转图片工具&#xff0c;交互和颜值都做得很漂亮 现在又新出了一个 图标制作器&#xff0c;一键制作美观好看的图标 猫啃网 没想到像【汇文明朝体】这样免费的字体都被人拿来当成【打字机字…

Gin框架操作指南03:HTML渲染

官方文档地址&#xff08;中文&#xff09;&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;本教程采用工作区机制&#xff0c;所以一个项目下载了Gin框架&#xff0c;其余项目就无需重复下载&#xff0c;想了解的读者可阅读第一节&#xff1a;Gin操作指南&#…

2024 “源鲁杯“ Round[1] web部分

Disal 打开页面没有有用信息&#xff0c;查看robots.txt发现f1ag.php&#xff0c;访问查看源代码&#xff1a; &#xfeff;<?php show_source(__FILE__); include("flag_is_so_beautiful.php"); $a$_POST[a]; $keypreg_match(/[a-zA-Z]{6}/,$a); $b$_REQUEST[…

【2024最新版】网络安全学习路线-适合入门小白

首先说明&#xff0c;我是一名CTF的web手&#xff0c;这是我自己亲身学习网络安全的路线&#xff0c;希望能够帮到大家&#xff0c;我虽然不是大牛&#xff0c;但我也希望能够帮助一些网安小白找到自己学习的方向&#xff0c;后面有就业的详细安全技术要求&#xff0c;如果真想…

NSSCTF-WEB-easy_eval

目录 前言 正文 思路 序列化构造 后渗透 思路点1:Redis 思路2:蚁剑插件绕过disable_functinons 结尾 作者的其他文章 前言 说是easy,实际很difficult 正文 思路 <?php class A{public $code "";function __call($method,$args){//最后执行命令eval($th…

(AtCoder Beginner Contest 375)A - Seats

&#xff08;AtCoder Beginner Contest 375&#xff09;A - Seats 题目大意 给定一个长度为 N N N的字符串 S S S S S S 只包含"#“和”." 求 "#.#"子串 的出现次数 思路 签到题 O ( N ) O(N) O(N) 模拟即可 代码 #include<iostream> #includ…

ssm配置模式

新版 用Java类&#xff0c;全注解demo案例 1. AppConfig.java (Spring主配置类)package com.example.config;import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.cont…

SpringCloudAlibaba升级手册

目录 1. 版本对照 版本现状 SpringCloud与AlibabaCloud对应版本 Springboot与Elasticsearch版本对应 2. openfeign问题 问题 解决方案 3. Feign请求问题 问题 解决方法 4. Sentinel循环依赖 问题 解决方案 5. bootstrap配置文件不生效 问题 解决方案 6. Nacos的…

工信部绿色工厂、绿色设计产品、绿色供应链企业、绿色园区名单(2017-2022年)

我国工信部积极推动制造业的绿色转型&#xff0c;为了表彰在绿色制造领域取得显著成绩的企业和园区&#xff0c;发布了包括绿色工厂、绿色设计产品、绿色供应链企业、绿色园区在内的一系列公示名单。 2017年-2022年工信部绿色工厂、绿色设计产品、绿色供应链企业、绿色园区名单…

脉冲扩散模型

论文 Spiking Diffusion Models 主要内容是提出了“脉冲扩散模型&#xff08;Spiking Diffusion Models, SDMs&#xff09;”&#xff0c;一种基于脉冲神经网络&#xff08;SNN&#xff09;的生成模型&#xff0c;旨在解决传统人工神经网络&#xff08;ANN&#xff09;在图像生…

5G NR:UE初始接入信令流程浅介

UE初始接入信令流程 流程说明 用户设备&#xff08;UE&#xff09;向gNB-DU发送RRCSetupRequest消息。gNB-DU 包含 RRC 消息&#xff0c;如果 UE 被接纳&#xff0c;则在 INITIAL UL RRC MESSAGE TRANSFER 消息中包括为 UE 分配的低层配置&#xff0c;并将其传输到 gNB-CU。IN…

2012年国赛高教杯数学建模C题脑卒中发病环境因素分析及干预解题全过程文档及程序

2012年国赛高教杯数学建模 C题 脑卒中发病环境因素分析及干预 脑卒中&#xff08;俗称脑中风&#xff09;是目前威胁人类生命的严重疾病之一&#xff0c;它的发生是一个漫长的过程&#xff0c;一旦得病就很难逆转。这种疾病的诱发已经被证实与环境因素&#xff0c;包括气温和湿…

怎么开发一款app软件

我们公司想要做一个app软件&#xff0c;老板就让我多了解几家&#xff0c;我就总计一下相关的市场行业。 8月份我一共了解了6家的软件开发公司&#xff0c;也见识了什么叫软件开发公司&#xff0c;6套下来我也挑花了眼&#xff0c;老板也就更不用说了。老板只差让我做选择了…

Linux操作系统切换设置系统语言

随着工作环境中变化&#xff0c;我们在使用电脑时&#xff0c;可能要使用不同的系统语言环境&#xff0c;那计算机如何切换成我们需要的系统语言呢&#xff0c;针对Linux操作系统&#xff0c;这里有两种方法。一是通过桌面图形化界面切换&#xff0c;这种方法操作起来直观、但是…

R语言复杂抽样调查数据统计描述和分析

gtsummary包中tbl_svysummary提供了统计描述&#xff1b;tableone包中的svyCreateTableOne提供了统计比较&#xff1b;原始描述和比较可以是有table1包。 #测试数据 library(survey) setwd("F://") data(Titanic) sur_des<-survey::svydesign(~1, data as.data.…

mongoDB基础知识

文章目录 为什么使用mongoDB&#xff1f;数据模型 想了解什么mongoDB首先我们得先知道什么事“三高”&#xff1f;三高什么呐&#xff1f;具体的应用场景&#xff1f;在这些应用场景中&#xff0c;数据操作方面共同特点是&#xff1a; 什么时候选择mongoDB&#xff1f;mySQL和m…

React基础知识(一) - React初体验

React是在2013年&#xff0c; 由Faceboo开源的&#xff0c;用于构建用户界面的 JavaScript 库。总所周知啊,React是前端三大框架之一啊现在前端领域最为流行的是三大框架&#xff1a; ReactVueAngular 前端三大框架 React开发的特点&#xff1a; &#xff08;1&#xff09;声明…