JavaScript系列——闭包

文章目录

    • 闭包定义
    • 词法作用域
    • 闭包示例
    • 使用场景
      • 创建私有变量
      • ES5 中,解决循环变量的作用域问题
    • 小结

闭包定义

闭包,是函数及其关联的周边环境的引用的组合,在闭包里面,内部函数可以访问外部函数的作用域,而外部函数不能范围内部函数的作用域,从而在内部函数形成一个相对封闭的环境。在JavaScript中,闭包随着函数创建而被创建

词法作用域

词法作用域指的是在源码声明的变量,能够起作用的环境范围,一般是从变量所定义的位置来决定。

看以下代码:

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

init() 创建了一个局部变量 name 和一个名为 displayName() 的函数。
displayName() 是定义在 init() 里的内部函数,并且仅在 init() 函数体内可用。
请注意,displayName() 没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 init() 中声明的变量 name 。

在ES6出现以前,声明变量使用 var 关键字,这样声明的变量,其实词法作用域是全局的。在此之前,会遇到很多奇怪的问题,闭包的出现,就是为了解决这些问题。

闭包示例

我们将刚刚的init 方法改造一下

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

运行这段代码的效果和之前 init() 函数的示例完全一样。其中不同的地方(也是有意思的地方)在于内部函数 displayName() 在执行前,从外部函数返回

第一眼看去,执行了makeFunc,name 变量应该会无法再被访问了,然而,执执myFunc 函数是,能够正常弹出Mozilla。

原因在于,在 makeFunc 中的函数中,形成了闭包。闭包是由函数以及声明该函数的词法环境组成。改环境包含了闭包创建时,作用域内的任何布局变量。

在上面的代码中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用
displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。
所以,当myFunc 被调用时,变量name 仍然可用。

闭包可以使得引用的变量可以不被垃圾回收机制回收,下面的例子可以加强理解:

function makeAdder(x) {return function (y) {return x + y;};
}var add5 = makeAdder(5);
var add10 = makeAdder(10);console.log(add5(2)); // 7
console.log(add10(2)); // 12

makeAdder 执行时,创建了一个闭包,makeAdder(5) 传入x =5,x 被 makeAdder 内部函数引用,因此执行了makeAdder后,x 依然保持在内存中。调用add5(2),返回 x+2 = 5+2= 7。

使用场景

创建私有变量

在 ES6之前,JavaScript不支持声明一个私有变量,使得变量不能在某个作用域外变得不可访问。
虽然JavaScript没有原生支持,但我们可以通过闭包来模拟私有方法和变量。

通过模拟闭包,不仅可以实现变量和方法私有,还可以实现避免同名变量和方法污染全局

看以下代码:

var Counter = (function () {var privateCounter = 0;function changeBy(val) {privateCounter += val;}return {increment: function () {changeBy(1);},decrement: function () {changeBy(-1);},value: function () {return privateCounter;},};
})();console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

在上面代码中,定义一个Counter 变量,他是一个立即执行函数,返回了三个函数。

这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。

如果我们试图访问privateCounter ,都是无法访问的。
如果想要访问privateCounter 的值,必须通过value 方法。要想改变值,只能通过increment或decrement方法,这样就实现了privateCounter 的私有化。

privateCounter 因为被内部函数changeBy引用,而返回的方法中保持了对changeBy引用,因此关于changeBy所引用的变量,将会保持在内存中,因此每一次通过方法改变privateCounter 的值,下一次访问它,就是他最新的值,而不是0。

每次创建的闭包,他们内部都是互相独立的,互不影响的
看下面的代码,分别维护各个作用域:

var makeCounter = function () {var privateCounter = 0;function changeBy(val) {privateCounter += val;}return {increment: function () {changeBy(1);},decrement: function () {changeBy(-1);},value: function () {return privateCounter;},};
};var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

两个计数器 Counter1 和 Counter2 维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。

每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。

ES5 中,解决循环变量的作用域问题

在 ES5时候,如果我们想要实现这个一个效果:
当我们把鼠标聚焦在某一个表单中,会有标题提示我们要填写的内容,我们很容易想到下面使用var 循环来实现:

<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);};}
}

执行上面的代码后效果如下图,无论把鼠标聚焦在哪,标题都是提示最后一个age 的提示,无法达到预期的效果。
原因是 在setupHelp 函数内部,for 循环共享的是同一个 item变量,当onfocus 被触发执行时,i已经变成了2,因此,无论聚焦哪个,最后都是触发第三个item。
在这里插入图片描述
解决这个问题的一种方案是使用更多的闭包:特别是使用前面所述的函数工厂:

function showHelp(help) {document.getElementById("help").innerHTML = help;
}function makeHelpCallback(help) {return function () {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);}
}setupHelp();

这份代码中,makeHelpCallback 内部创建了闭包,使得传入makeHelpCallback的item,可以在其内部保持其独立性

通过for 循环,分别创建了三个闭包,每一个闭包都是互相独立的。而且makeHelpCallback外部的item变化,不影响makeHelpCallback内部的值,因为闭包里面词法环境是独立的,不与函数外部的item词法环境共享值,因此即使外部item 变化,也不影响闭包的词法环境所引用的变量值。

小结

  • 闭包是一个函数及其词法环境的引用的组合
  • 闭包内部的词法环境(变量的作用域)是独立的,每次执行函数,都会创建新的闭包,每次创建的闭包都有其独立的词法环境
  • 闭包内部所引用的变量不会随着函数执行而被销毁,而会一直保持在内存中
  • 闭包可以用于模拟私有变量和方法和解决 var循环共享值问题。

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

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

相关文章

java通过HttpClient方式实现https请求的工具类(绕过证书验证)

目录 一、引入依赖包二、HttpClient方式实现的https请求工具类三、测试类 一、引入依赖包 引入相关依赖包 <!--lombok用于简化实体类开发--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><option…

AD软件与其他EDA软件工程的问题汇总

1:如何在AD中使用eagle工程 在ad中打不开原理图&#xff0c;要使用导入功能,转化为ad的文件后&#xff0c;就可以打开了 2:打开旧版本的Protel文件 有时候新版本的AD打不开以前Protel的PCB文件&#xff0c;可以在DXP菜单下的Extension下进行配置&#xff08;Configure&…

求幸存数之和 - 华为OD统一考试

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 给一个正整数列nums,一个跳数jump,及幸存数量left。运算过程为:从索引为0的位置开始向后跳,中间跳过 J 个数字,命中索引为 J+1 的数字,该数被敲出,并从该点起跳,以此类推,直到幸存left个数为止。…

制造知识普及--MES系统中的调度排产管理

要想弄清楚MES系统调度排产的管理机制&#xff0c;则要首先搞清楚车间调度排产是一套怎样的工作流程&#xff0c;它的难点在什么地方&#xff1f; 生产调度指的是具体组织实现生产作业计划的工作&#xff0c;是对执行生产作业计划过程中发生的问题和可能出现的问题&#xff0c…

工业以太网的网络安全与数据传输性能

工业以太网主要是一种用于工业控制系统的网络通信协议&#xff0c;它基于以太网技术&#xff0c;将其应用于工业环境中&#xff0c;以实现高速、可靠、安全的数据传输。跟传统的专用工业网络比较&#xff0c; 工业以太网具有更大的带宽、更低的成本以及更好的扩展性&#xff0c…

轮询定时器 清除 + vue2.0

需求? Gin Vue Element UI框架中, 我的大屏可视化项目, 大屏页面, 里边写了多个轮询定时器. 离开页面需要清理掉, 要不然切换路由还会在后台运行, 页面是自动缓存状态, 也不存在销毁一说了 所以通过路由router配置中, 页面路由监听中, 进行监听路由变化, 但是也没生效 …

MySQL中datetime和timestamp的区别

datetime和timestamp的区别 相同点: 存储格式相同 datetime和timestamp两者的时间格式都是YYYY-MM-DD HH:MM:SS 不同点: 存储范围不同. datetime的范围是1000-01-01到9999-12-31. 而timestamp是从1970-01-01到2038-01-19, 即后者的时间范围很小. 与时区关系. datetime是存储…

Vue2 实现带输入的动态表格,限制el-input输入位数以及输入规则(负数、小数、整数)

Vue2 实现el-input带输入限制的动态表格&#xff0c;限制输入位数以及输入规则&#xff08;负数、小数、整数&#xff09; 在这个 Vue2 项目中&#xff0c;我们实现一个限制输入位数&#xff08;整数16位&#xff0c;小数10位&#xff09;以及输入规则&#xff08;负数、小数、…

Android 11.0 mtp模式下连接PC后只显示指定文件夹功能实现

1. 前言 在android11.0的系统rom定制化开发中,对于usb作为otg连接电脑时,在mtp模式下会作为一个存储器在电脑端显示,作为电脑的 一个盘符,来显示设备的内部存储的文件,所以说如果要对设备内部的资料做保密处理的时候,需要在mtp模式下不显示某些 文件夹,接下来就分析下相…

Fluids —— Narrow band and variable density

目录 Narrow Band Variable Density Narrow Band Narrow band是一种有效的加速模拟、节省资源、及优化整体性能的方法&#xff1b;其想法是&#xff0c;只在表面上带有一定厚度的粒子&#xff0c;表面下的一切都不是通过粒子表示的&#xff1b; 具有大量粒子的模拟&#xff0…

Leetcode2981. 找出出现至少三次的最长特殊子字符串 I

Every day a Leetcode 题目来源&#xff1a;2981. 找出出现至少三次的最长特殊子字符串 I 解法1&#xff1a;滑动窗口 暴力枚举 滑动窗口枚举窗口内字符相同的字符串&#xff0c;再暴力枚举长度相等的字符串。 代码&#xff1a; /** lc appleetcode.cn id2981 langcpp**…

DHCP,怎么在Linux和Windows中获得ip

一、DHCP 1.1 什么是dhcp DHCP动态主机配置协议&#xff0c;通常被应用在大型的局域网络环境中&#xff0c;主要作用是集中地管理、分配IP地址&#xff0c;使网络环境中的主机动态的获得IP地址、DNS服务器地址等信息&#xff0c;并能够提升地址的使用率。 DHCP作为用应用层协…

DRAM、SRAM、PSRAM和Flash

DRAM、SRAM和Flash都属于存储器&#xff0c;DRAM通常被称为内存&#xff0c;也有些朋友会把手机中的Flash闪存误会成内存。SRAM的存在感相对较弱&#xff0c;但他却是CPU性能发挥的关键。DRAM、SRAM和Flash有何区别&#xff0c;它们是怎样工作的&#xff1f; DRAM&#xff1a;…

Multimodal Knowledge Expansion复现

表2 复现结果 multimodal student (ours)&#xff1a; v 0 2 v 1 10 r 0 0.8 82.1 78.6 77.5 \begin{array}{} v02 & v1 10 & r0 0.8 \\\\ 82.1 & 78.6 & 77.5 \end{array} v0282.1​v11078.6​r00.877.5​ 感想 第二篇完全复现的论文

创建型模式 | 建造者模式

一、建造者模式 1、原理 建造者模式又叫生成器模式&#xff0c;是一种对象的构建模式。它可以将复杂对象的建造过程抽象出来&#xff0c;使这个抽象过程的不同实现方法可以构造出不同表现&#xff08;属性&#xff09;的对象。创建者模式是一步一步创建一个复杂的对象&#xf…

zookeeper下载安装部署

zookeeper是一个为分布式应用提供一致性服务的软件&#xff0c;它是开源的Hadoop项目的一个子项目&#xff0c;并根据google发表的一篇论文来实现的。zookeeper为分布式系统提供了高效且易于使用的协同服务&#xff0c;它可以为分布式应用提供相当多的服务&#xff0c;诸如统一…

P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布————C++

目录 [NOIP2014 提高组] 生活大爆炸版石头剪刀布题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示 解题思路Code调用函数的Code&#xff08;看起来简洁一点&#xff09;运行结果 [NOIP2014 提高组] 生活大爆炸版石头剪刀布 …

软件测评中心▏性能测试之压力测试、负载测试的区别和联系简析

在如今的信息时代&#xff0c;软件已经成为人们日常工作和生活不可或缺的一部分。然而&#xff0c;随着软件的发展和应用范围的不断扩大&#xff0c;软件性能的优劣也成为了影响用户使用体验的重要因素。 软件性能测试即对软件在不同条件下的性能进行评估和验证的过程。通过模…

php多小区智慧物业管理系统源码带文字安装教程

多小区智慧物业管理系统源码带文字安装教程 运行环境 服务器宝塔面板 PHP 7.0 Mysql 5.5及以上版本 Linux Centos7以上 统计分析以小区为单位&#xff0c;统计如下数据&#xff1a;小区总栋数、小区总户数、小区总人数、 小区租户数量、小区每月收费金额统计、小区车位统计、小…

【Scala】——变量数据类型运算符

1. 概述 1.1 Scala 和 Java 关系 1.2 scala特点 Scala是一门以Java虚拟机&#xff08;JVM&#xff09;为运行环境并将面向对象和函数式编程的最佳特性结合在一起的静态类型编程语言&#xff08;静态语言需要提前编译的如&#xff1a;Java、c、c等&#xff0c;动态语言如&#…