JS之闭包

news2024/10/7 14:32:40

在这里插入图片描述

一:什么是闭包

闭包是一个函数和其词法环境的组合

换个意思来说,闭包可以让开发者可以从函数内部访问到外部函数的作用域

在JS中,闭包会随着函数的创建而被同时创建

词法环境

主要分两个对象

用于管理变量函数和作用域的关系

  • 环境记录:存储变量和函数声明的地方
  • 对外部环境的引用:指向包含当前词法环境的外部词法环境的引用。这使得内部环境可以引用外部环境的变量。这种引用关系形成了作用域链

词法环境例子

function init() {
  var name = "Mozilla"; // name 是一个被 init 创建的局部变量
  function displayName() {
    // displayName() 是内部函数,一个闭包
    alert(name); // 使用了父函数中声明的变量
  }
  displayName();
}
init();

执行init()函数后,可以成功突出name变量的值,displayName函数的词法作用域可以引用外部Init函数词法作用域中的name变量

image-20230908192823064

闭包例子:

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

  • 执行makeFunc函数,创建makeFunc函数,在之前displayName函数之前将该函数返回,myFunc变量是displayName函数的实例
  • makeFunc函数创建的局部变量name,在displayName函数中被引用
  • myFunc维持了displayName函数的词法环境的一个引用,使得makeFunc函数在执行结束后,变量name仍然可用

二:使用场景

  • 创建私有变量
  • 延长变量的生命周期

如果在执行上下文中创建的函数是一个闭包,并且在闭包中引用了外部词法环境的变量,那么该词法环境不会在执行上下文销毁后立即被销毁。相反,词法环境会被保留,直到不再有任何引用指向闭包或相关的词法环境。这是因为闭包需要持续访问外部变量,所以相关的词法环境不能被销毁。

事件处理

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

使用闭包创建和分配事件处理函数

  • makeSizer函数创建闭包,makeSizer返回一个匿名函数,并接收一个参数size,匿名函数捕获了外部函数的size变量,使得即使makeSizer函数执行结束后,也使得内部函数可以访问外部size变量
  • 事件处理函数内部执行,当用户点击链接时,与链接相关的事件处理函数(闭包)被触发。这些函数内部访问了它们创建时捕获的字体大小

相比将事件处理函数直接赋值给onclick属性的区别

  • 在作用域和数据封装方面,事件处理函数被封装到一个自包含的作用域中,它可以通过捕获外部函数的size变量,而避免将该变量引入到全局作用域中,避免全局变量污染。直接设置事件处理函数,是在全局作用域去设置事件处理函数,可能会导致全局命名函数的命名冲突,和污染全局变量
  • 动态性和复用性:直接设置事件处理函数想要设置不同的字体大小需要设置三个不同的事件处理函数,而闭包实现,可以通过传递不同的size参数,然后由事件处理函数捕获外部size值进行操作赋值

使用闭包模拟私有方法

JS可以通过闭包来模拟私有方法,即该私有属性只能被该类上的其余方法调用,而无法被外界直接使用

这种方式也称为模块化

    function Counter ()
    {
      // 私有变量
      var count = 0;

      // 私有方法
      function increment ()
      {
        count++;
      }

      // 公共方法,可以访问私有方法和私有变量
      return {
        getCount: function ()
        {
          return count;
        },
        incrementAndReturnCount: function ()
        {
          increment();
          return count;
        }
      }
    }

    var myCounter = new Counter();

    console.log(myCounter.getCount()); // 输出: 0
    console.log(myCounter.incrementAndReturnCount()); // 输出: 1
    console.log(myCounter.getCount()); // 输出: 1
    console.log(myCounter.count); //输出: undefined

  • 通过闭包模拟私有方法
    • 在Counter构造函数内部创建count私有变量和increment私有方法
    • 通过在Counter函数中返回两个公共方法getCount和incrementAndReturnCount方法,通过闭包,getCount的词法环境可以引入外部Counter的词法环境的变量
    • 因此外部函数只能通过公共方法间接访问私有方法和修改私有属性

以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。

在循环中创建闭包

该案例想要实现,当三个不同的文本框获得光标时候,触发事件显示对应三个不同的文本内容

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email" /></p>
<p>Name: <input type="text" id="name" name="name" /></p>
<p>Age: <input type="text" id="age" name="age" /></p>

function showHelp(help) {
  document.getElementById("help").innerHTML = help;
}

function setupHelp() {
  var helpText = [
    { id: "email", help: "Your e-mail address" },
    { id: "name", help: "Your full name" },
    { id: "age", help: "Your age (you must be over 16)" },
  ];

  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function () {
      showHelp(item.help);
    };
  }
}

setupHelp();

在该代码中出现错误,三个文本框显示的都是同一个内容

image-20230908221700386

发现错误

在setupHelp函数中,想要循环给三个文本框添加三个不同的事件,用var关键字声明item变量,由于var声明的变量具体变量提升特性,导致item变量的作用域是函数作用域,这会使得最终三个文本框的事件处理函数中的item指向的是同一个item变量,由于最终item变量值指向helpText的最后一个内容,所以三个文本框显示的是同一个内容

使用匿名闭包处理

      for (var i = 0; i < helpText.length; i++) {
        (function ()
        {
          var item = helpText[i];
          // console.log('item = ' + item.help);
          document.getElementById(item.id).onfocus = function ()
          {
            console.log('x');
            showHelp(item.help);
          };
        })()
      }
  • 通过匿名闭包函数,将每次迭代的item变量通过匿名闭包函数包裹起来,这个闭包函数会立即执行,因此每次迭代都会产生独立的词法环境用于存储item的副本,避免了共享的问题
  • 文本框的 onfocus 事件处理函数被设置为一个闭包函数。这个闭包函数可以访问自己的局部作用域,因此它能够正确地引用其所关联的 item 变量,显示正确的帮助文本。

onfous事件接收一个闭包

    function makeHelpCallback (help)
    {
      return function ()
      {
        console.log(help);
        showHelp(help);
      };
    }

    function setupHelp ()
    {
      var helpText = [
        { id: "email", help: "Your e-mail address" },
        { id: "name", help: "Your full name" },
        { id: "age", help: "Your age (you must be over 16)" },
      ];

      for (var i = 0; i < helpText.length; i++) {
        var item = helpText[i];
        document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
      }
    }
  • makeHelpCallback 函数接受一个参数 help,然后返回一个新的函数,这个新函数就是回调函数。这个回调函数的作用是在文本框获得焦点时显示相应的帮助文本。
  • 当在循环中调用 makeHelpCallback(item.help) 时,它会根据传递给它的 help 参数的值,创建一个独立的回调函数。这个回调函数“记住”了它创建时的 help 参数的值。
  • 因为每次循环迭代都会调用 makeHelpCallback,所以每次迭代都会创建一个新的回调函数,而且这些回调函数之间是相互独立的,它们没有共享相同的内部状态。
  • 当某个文本框获得焦点时,对应的独立回调函数会执行。这个回调函数使用自己“记住”的 help 参数的值,来显示正确的帮助文本。由于每个回调函数都有自己独立的 help 参数,所以它们能够正确地显示不同的帮助文本,而不会混淆或共享数据。

使用let声明item变量

      for (var i = 0; i < helpText.length; i++) {
        var item = helpText[i];
        document.getElementById(item.id).onfocus =  function ()
          {
            console.log('x');
            showHelp(item.help);
          };;
      }

let作用域是块级作用域,每一次迭代都会声明不同的item变量,因此每个 onfocus 事件处理函数都捕获了自己迭代中的 item

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

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

相关文章

《深入理解Java虚拟机》——Java内存区域与内存溢出异常

Java内存区域与内存溢出异常 运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存 实例堆溢出栈溢出 运行时数据区域 根据《Java虚拟机规范的规定》&#xff0c;Java虚拟机所管理的内存将会包含已下架几个运行时数据区域。 程序计数器 在Java虚…

【漏洞复现】EnjoySCM存在文件上传漏洞

漏洞描述 EnjoySCM是一款适应于零售企业的供应链管理软件,主要为零售企业的供应商提供服务。EnjoySCM的目的是通过信息技术,实现供应商和零售企业的快速、高效、准确的信息沟通、管理信息交流。。 该系统存在任意文件上传漏洞,攻击者通过漏洞可以获取服务器的敏感信息。 …

k8s入门到实战--跨服务调用

service.png 背景 在做传统业务开发的时候&#xff0c;当我们的服务提供方有多个实例时&#xff0c;往往我们需要将对方的服务列表保存在本地&#xff0c;然后采用一定的算法进行调用&#xff1b;当服务提供方的列表变化时还得及时通知调用方。 student: url: - 192.168.1…

【STM32】STM32F4调用DSP库实现FFT运算

写在前面 最近在整理之前的stm32笔记&#xff0c;打算把一些有价值的笔记发到CSDN分享一下。 奎斯特定理 在进行模拟/数字信号的转换过程中&#xff0c;当采样频率F大于信号中最高频率 fmax 的 2 倍时(F>2*fmax)&#xff0c;采样之后的数字信号完整地保留了原始信号中的信…

高效采集模拟量模块数据方案

在现代工业自动化领域&#xff0c;模拟量采集是关键的环节之一。本文将详细介绍如何通过模拟量采集电压、电流和温度等数据&#xff0c;并利用上位机实现数据的获取和转化。同时&#xff0c;我们还将详细介绍模拟量采集上位机框架及其强大的功能&#xff0c;为企业实现高效的数…

华为认证系统学习大纲及课程

前言 任何学习过程都需要一个科学合理的学习路线&#xff0c;才能够有条不紊的完成我们的学习目标。华为认证网络工程师所需学习的内容纷繁复杂&#xff0c;难度较大&#xff0c;所以今天特别为大家整理了一个全面的华为认证网络工程师学习大纲及课程&#xff0c;帮大家理清思…

【DataV/echarts】vue中使用,修改地图和鼠标点击部分的背景色

引入&#xff1a;使用 DataV 引入地图的教程是参考别人的&#xff0c;主要介绍修改地图相关的样式&#xff1b; 引入地图 是参考别人的&#xff0c;这里自己再整理一遍&#xff0c;注意需要安装 5 版本以上的 echarts&#xff1b; DataV 网址&#xff1a;https://datav.aliyun.…

Unity Animation、Animator 的使用(超详细)

文章目录 1. 添加动画2. Animation2.1 制作界面2.2 制作好的 Animation 动画2.3 添加和使用事件 3. Animator3.1 制作界面3.2 一些参数解释3.3 动画参数 4. Animator中相关类、属性、API4.1 类4.2 属性4.3 API4.4 几个关键方法 5. 动画播放和暂停控制 1. 添加动画 选中待提添加…

Win10如何清理无效注册表

电脑中部分注册表文件其实是没有什么用的&#xff0c;如果用户不主动清理的话就会占用大量的内存空间&#xff0c;从而导致系统变得卡顿&#xff0c;那么Win10怎么清理无效注册表呢&#xff0c;下面小编就给大家详细介绍一下Win10清理无效注册表的方法&#xff0c;大家感兴趣的…

无涯教程-JavaScript - IMCOS函数

描述 IMCOS函数以x yi或x yj文本格式返回复数的余弦。 语法 IMCOS (inumber)争论 Argument描述Required/OptionalInumberA Complex Number for which you want the cosine.Required Notes Excel中的复数仅存储为文本。 当将格式为" a bi"或" a bj&quo…

门口通畅家运顺

每一次遇见&#xff0c;都是一个心愿&#xff0c;也许&#xff0c;前有未了的情缘&#xff0c;所以&#xff0c;此生才能得以见面&#xff0c;所有的遇见&#xff0c;一切都是最好的安排。前段时间&#xff0c;峰民再次故地重游&#xff0c;去到了呼伦比尔海拉尔区为预约客户来…

《protobuf》基础语法

文章目录 消息体定义字段规则编译选项实战&#xff1a;编写一个通讯录文件 消息体定义 文件内定义 message Phone {string number 1; }message PeopleInfo {string name 1;int32 age 2;Phone phone 3; }内嵌定义 message PeopleInfo {string name 1;int32 age 2;messa…

如何自启动MySQL服务与解决MySQL字符集问题

1、自启动mysql服务 &#xff08;1&#xff09;查看mysql是否自启动&#xff08;默认自启动&#xff09; systemctl list-unit-files|grep mysqld.service &#xff08;2&#xff09;如不是enabled可以运行如下命令设置自启动 systemctl enable mysqld.sercice2、字符集…

[DM8] DM-DM DBLINK DPI方式

前言 对于DM与DM之间的DBLINK&#xff0c;三种方式中&#xff0c;使用DPI方式配置上最为方便&#xff0c;ODBC方式需要安装ODBC包并配置ODBC数据源&#xff0c;dmmal方式需要设置MAL_INI数据库参数、配置dmmal.ini文件并需要重启数据库服务。 dpi类型的dblink&#xff0c;达梦…

eNSP与CRT配置

1、启动所有设备 2、右键设备&#xff0c;进入“设置” 3、在设置界面中&#xff0c;进入“配置选项卡”&#xff0c;记住串口号 4、打开CRT&#xff0c;进行快速连接 5、协议选择Telnet、Hostname输入“127.0.0.1”、端口输入设备的串口号 6、最终连接效果 eNSP连接CRT配置t…

2023年9月7日

1> 封装一个结构体&#xff0c;结构体中包含一个私有数组&#xff0c;用来存放学生的成绩&#xff0c;包含一个私有变量&#xff0c;用来记录学生个数&#xff0c; 提供一个公有成员函数&#xff0c;void setNum(int num)用于设置学生个数 提供一个公有成员函数&#xff1…

Nougat:一种用于科学文档OCR的Transformer 模型

随着人工智能领域的不断进步&#xff0c;其子领域&#xff0c;包括自然语言处理&#xff0c;自然语言生成&#xff0c;计算机视觉等&#xff0c;由于其广泛的用例而迅速获得了大量的普及。光学字符识别(OCR)是计算机视觉中一个成熟且被广泛研究的领域。它有许多用途&#xff0c…

测试岗位的不足和缺点-思考

软件测试岗位在实际工作中可能会面临一些不足和缺点&#xff0c;以下是一些常见的问题&#xff1a; 高压力、高强度的工作&#xff1a;软件测试工作往往需要在项目截止日期前完成测试&#xff0c;这可能会带来巨大的压力。同时&#xff0c;如果开发团队在项目中进行了大量的更改…

shell脚本详解

当你进入Linux世界的大门时&#xff0c;就会遇到一个强大而又神奇的工具——Shell。Shell是一种命令行解释器&#xff0c;为你在Linux系统中与计算机进行互动提供了无限的可能性。 学习Shell可以让你获得强大的自动化和脚本编程能力&#xff0c;让你更高效地处理文件和目录、管…

微信小程序使用 scss

一、在 vscode 中安装 easy sass 扩展 二、在微信开发者工具导入 vscode 安装的 easy sass 扩展 安装完成后会让重新加载扩展 再次打开后就可以看到扩展已经有 easy sass 了 三、修改 easy sass 配置 重新加载扩展后&#xff0c;默认情况下这个扩展是已经启动的&#xff0c…