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.hqwc.cn/news/342290.html

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

相关文章

家政服务管理平台

&#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;一 、设计说明 1.1选题的背景 现…

Komodor 为 Kubernetes 监控和故障排除带来了什么

为了方便起见&#xff0c;Komodor 提供了一个简单的 Web 界面&#xff0c;以帮助您监控 Kubernetes 集群的状态。它拥有付费和免费增值计划&#xff0c;除了在出现问题时通知用户外&#xff0c;还拥有一系列方便的工具&#xff0c;用于跟踪和管理集群中部署的资源的状态。让我们…

php通用后台开发框架源码

php通用后台开发框架源码 基于ThinkPHPBootstrap的快速后台开发框架。 基于Auth验证的权限管理系统&#xff0c;支持无限级父子级权限继承&#xff0c;父级的管理员可任意 增删改子级管理员及权限设置&#xff0c;支持单管理员多角色&#xff0c;支持管理子级数据或个人数据。 …

安泰电子功率放大器在设计电路时应该注意什么

在设计功率放大器电路时&#xff0c;有几个重要的因素需要特别注意。这些因素包括功率放大器的线性度、效率、稳定性、保护功能和适当的散热设计。下面将详细介绍每个因素&#xff0c;并说明在设计功率放大器电路时应该注意的要点。 线性度&#xff1a; 功率放大器的线性度是指…

【快刊录用】ABS一星,2区,仅2个月15天录用!

2023年12月30日-2024年1月5日 进展喜讯 经核实&#xff0c;由我处Unionpub学术推荐的论文中&#xff0c;新增2篇论文录用、3篇上线见刊、1篇数据库检索&#xff1a; 录用通知 FA20107 FA20181 — 见刊通知 FB20805 FA20269 FA20797 检索通知 FA20199 — — 计算机…

科研学习|论文解读——信息世界映射方法

题目&#xff1a;信息世界映射的下一步是什么&#xff1f;在情境中理解信息行为/实践的国际化和多学科方法&#xff08;What is next for information world mapping? International and multidisciplinary approaches to understanding information behaviors/ practices in …

4.2 MATRIX MULTIPLICATION

矩阵-矩阵乘法&#xff0c;或简称矩阵乘法&#xff0c;在 i X j&#xff08;i 行 by j 列&#xff09;矩阵 M 和 j x k 矩阵 N 之间产生 i X k 矩阵P。矩阵乘法是基本线性代数子程序&#xff08;BLAS&#xff09;标准的重要组成部分&#xff08;见第3章中的“线性代数函数”边栏…

SSM框架学习笔记04 | SpringMVC

文章目录 一、SpringMVC简介二、 请求与响应1. 请求映射路径2. get请求与post请求3. 响应 二、REST风格1.简介 三、 SSM整合四、拦截器1. 定义拦截器2.配置拦截器3.拦截器执行顺序4.拦截器参数5.多个连接器工作流程分析6.拦截器链的运行顺序 一、SpringMVC简介 SpringMVC技术与…

C++力扣题目112,113--路径总和,路径总和II

112路径总和 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 叶子节点 是…

网络编程套接字(Socket)

文章目录 1 重点知识2 预备知识2.1 理解源IP地址和目的IP地址2.2 认识端口号2.3 理解 "端口号" 和 "进程ID"2.4 理解源端口号和目的端口号2.5 认识TCP协议2.6 认识UDP协议2.7 网络字节序 3 socket编程接口3.1 socket 常见API3.2 sockaddr结构 4 简单的UDP网…

答题小程序源码系统:自带流量主广告位+视频激励广告 带完整的代码安装包以及搭建教程

随着互联网的迅速发展&#xff0c;各种应用程序层出不穷&#xff0c;而答题类小程序由于其独特的互动性和吸引力&#xff0c;成为了当前最热门的应用之一。答题小程序源码系统是一款基于微信小程序开发的源代码系统&#xff0c;它具有丰富的功能和灵活的定制性&#xff0c;可以…

UOS Python+Qt5实现声卡回路测试

1.回路治具设计&#xff1a; 2.Ui界面&#xff1a; 3.源代码&#xff1a; # -*- coding: utf-8 -*-# Form implementation generated from reading ui file SoundTestWinFrm.ui # # Created by: PyQt5 UI code generator 5.15.2 # # WARNING: Any manual changes made to this…