js什么是闭包?简单理解

news2024/11/17 7:37:55
闭包
作用域链和执行上下文

理解闭包前,先引入一个概念,作用域链

用我自己理解的讲:在一段程序中,程序内的变量、函数等都被串在这条链上,当我们使用这些变量、函数时,程序就会在这条链中搜索,如果没有找到你调用的变量或函数,就会出现问题

举个例子:

var a = 0,b = 1
function add(a, b){
	return a + b
}
//上面的a,b还有add就被串在了这个程序的作用域链中
console.log(add(a, b))
//调用add(a, b),程序就会在作用域链上搜索,找到add方法和a、b变量

这里我们的变量和函数都是定义在全局中(最外层)的,如果定义在函数内呢?

var a = 0
function foo(){
	var bar = 0
	console.log(a)//0    
}
//在foo外层访问bar变量
console.log(bar)//报错Uncaught ReferenceError: bar is not defined

为什么在这里报错说bar没有定义呢?

按照我的理解,作用域链分为子作用域链和父作用域链,本例变量a和函数foo串在外层作用域链中,而bar串在foo函数内的作用域链中,两条链的关系就和父子一样
在这里插入图片描述

当我们在全局(最外层)中访问a变量(console.log(a)),程序就会优先在全局作用域链(也就是上面的父作用域链)中查找a变量

当我们在foo函数中访问bar变量(console.log(bar),程序就会优先在foo作用域链中查找bar变量

到这里一切安好,直到我们在全局中访问foo函数中的bar变量,就出了问题,bar定义在foo作用域链中,程序在父作用域链中进行搜索,并不会进入子作用域链中,自然是搜索不到的,程序就会认为我们没有定义这个变量,就会报错

但我们在foo方法中访问a变量,为什么又可以了呢?

原来程序不仅仅会在子作用域链中搜索,还会上升到父作用域链,这时候foo链中找不到,就会去全局链中查找,自然能访问到变量a

到这里我们了解了程序在作用域链上的搜索是有范围的,它的范围就称作JavaScript中的执行上下文(简称上下文)

变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。(《JavaScript高级程序设计(第四版)》)

到这里就很明确啦,js中的函数有属于自己的作用域,在它自有的作用域中可以访问外部,但外部不能反向访问函数内部的变量

明白了作用域链和执行上下文的概念,接下来就看看闭包是什么

闭包

匿名函数经常被人误认为是闭包(closure)。闭包指的是那些引用了另一个函数作用域中变量的函数。(《JavaScript高级程序设计(第四版)》)

function foo(){
	var a = 0
	return function(){
		return a++
	}
}
var bar = foo()
bar()
console.log(a)//1
bar()
console.log(a)//2

这个例子乍一看有些奇怪,且等我为你一一道来

第一步很简单,我们定义了一个foo函数,在foo函数里定义了一个a变量

第二步,我们return了一个返回值,与往常不同的是,这次返回的值是一个匿名函数,匿名函数中实现了a++的功能

第三步,在下方我们定义了bar,调用foo函数,让它等于foo函数的返回值,也就是我们在第二步中定义的匿名函数,现在我们就可以认为,bar是一个函数,是foo中返回的匿名函数

第四步,我们就可以调用bar方法,实现a++的操作

现在你可能不明白我想要做什么,不要急,思考一个问题:这个例子的作用域链是什么样的呢?试着画一画

答案:

bar被赋值了foo中的匿名函数,bar自然就连接在了foo函数的作用域链上,根据我们上面提到的,作用域链内部可以访问外部,也就是说,bar可以访问foo中的变量a,也可以访问外部的全局变量

那我们为什么不直接在全局中定义a变量,然后对它操作呢?

实际上,如果我们将每个要使用的变量都定义为全局变量,就会导致全局变量过多,这对维护与优化来说并不是好事情

使用了闭包,我们将要使用的变量放在函数作用域中,使用函数作用域返回的函数来操作这个变量,就避免了将变量定义在全局中,在我们不需要它的时候直接为bar赋值为null,没办法再调用到那个匿名函数,程序就会自动检测到并回收foo作用域链,为我们清理出内存

这里对作用域链与闭包只做了简单介绍,像更深入理解请移步红宝书(《JavaScript高级程序设计(第四版)》)

扩展

使用自执函数优化闭包:

var bar = (function(){
	var a = 0
	return function(){
		a++;
	}
})
bar()
console.log(a)//1
bar()
console.log(a)//2

与前一个例子中不同的是,我没有定义foo函数,直接用一个自执行函数替代了foo,我们需要的只是foo对应的函数作用域链,它本身我们并不关心,所以我们可以像本例这样进行简写

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

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

相关文章

【最新】滤器完整性检测各国规定

中国 用于直接接触无菌药液或无菌设备表面的气体的过滤器,应在每批或多批次连续生产结束后对其进行完整性测试。对于其他的应用,可以根据风险评估的结果,制定完整性测试的频率。 ——除菌过滤技术与应用指南 2018 美国 We recommend that …

系统中的安全架构

系统中的安全架构目录概述需求:设计思路实现思路分析1.shiro2.多模块下的安全架构参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy,skip hardness,make a better result,wait for chan…

【网安神器篇】——searchsploit漏洞利用搜索工具

作者名:Demo不是emo 主页面链接:主页传送门 创作初心:舞台再大,你不上台,永远是观众,没人会关心你努不努力,摔的痛不痛,他们只会看你最后站在什么位置,然后羡慕或鄙夷座…

MyBatis是如何初始化的?

摘要:我们知道MyBatis和数据库的交互有两种方式有Java API和Mapper接口两种,所以MyBatis的初始化必然也有两种;那么MyBatis是如何初始化的呢?本文分享自华为云社区《MyBatis详解 - 初始化基本过程》,作者:龙…

golang 协程的实现原理

核心概念 要理解协程的实现, 首先需要了解go中的三个非常重要的概念, 它们分别是G, M和P, 没有看过golang源代码的可能会对它们感到陌生, 这三项是协程最主要的组成部分, 它们在golang的源代码中无处不在. G (goroutine) G是goroutine的头文字, goroutine可以解释为受管理的…

Java+MySQL基于ssm的学生宿舍管理系统

随着我国教育制度的改革,各大高校一直在不断的扩招相对应的学生的数量也在不断的增加。在学生数量增加之后学校后勤人员就需要对后勤部分更加精准的进行管理,其中宿舍管理就是后勤管理中比较重要的一个组成部分。如何能够对学生的宿舍信息进行更加科学合理的管理是当前大多数高…

Word文档误删怎样恢复?6种实用方法分享给你

如果您曾经因为没有保存微软Word文档而丢失了所有工作,那么您就会明白疼痛是多么明显。 幸运的是,自从在软盘上备份文件的黑暗时代以来,Word已经走过了漫长的道路。如今,如果您丢失了未保存的Word文档,可能仍然有一种…

31.Django大型电商项目之加入购物车——Django的增加、删除、修改、查询实操

1. 加入购物车 views # netshop\cartapp\views.py from django.shortcuts import render, redirect from django.http import HttpResponse, HttpResponseBadRequest from utils.cartmanager import * # Create your views here. # 购物车视图 def cartView(request):# 获取表…

高通Ride软件开发包使用指南(13)

高通Ride软件开发包使用指南(13)9.3使用HLOS验证PCIe交换机9.3.1先决条件发行说明9.3.2 PCIE设备枚举9.3.3验证SA9000推断9.3.4芯片对芯片9.3.5 NVME操作9.3.6 10Gb以太网9.3使用HLOS验证PCIe交换机 本节提供有关如何通过HLOS验证PCIe交换机用例的信息Q…

Set接口-HashSet和LinkedHashSet

1.Set 接口 1.1基本介绍 1)无序(添加和取出的顺序不一致),没有索引; 2)不允许重复元素,所以最多包含一个null; 3)JDK API中Set接口的实现类有: 1.2Set 接口的常用方法 和 List 接口一样, Set 接口也是 Collection 的子接口,因此,常用方法和Co…

Pytorch100例 | 用深度学习处理分类问题【实战教程】

PyTorch和TensorFlow库是用于深度学习的两个最常用的 Python 库。PyTorch 是 Facebook 开发的,而 TensorFlow 是 Google 的项目。在本文中,你将看到如何使用 PyTorch 库来解决分类问题。 分类问题属于机器学习问题的范畴,其中给定一组特征&am…

【檀越剑指大厂—SpringBoot】SpringBoot应用

一.配置 1.配置文件 SpringBoot 使用一个全局的配置文件,配置文件名称固定 application.propertiesapplication.yml 配置文件的作用:修改 SpringBoot 自动配置的默认值;SpringBoot 在底层都给我们自动配置好 2.tomcat 配置 server:port: 8081error…

IDEA下使用Git与GitHub【超详细】

IDEA结合Git 初始化Git及提交 查看提交版本 切换版本 创建分支与切换 合并分支 ​编辑 分支冲突 IDEA结合GitHub 创建GitHub账号 上传代码到本地仓库 推送代码 拉取代码 克隆远程库到本地 这里是在学习完Git的基础指令来了解在企业合作开发下如何用集成工具联合Git…

Photoshop - 高反差保留

对图像处理相关学习的一些笔记归档发表,关于锐化的原理; 首先简而言之,当颜色明度为100%的时候,为白色,反之为黑色: 为50%时,就是中性灰; 在混合方式中,变暗这一组&…

JVM之虚拟机栈

1. 虚拟机栈概述 虚拟机栈不存在GC,但存在OOM,程序计数器二者都不存在 2. 栈的存储单位 3. 局部变量表 变量的分类:按照数据类型分:① 基本数据类型 ② 引用数据类型 按照在类中声明的位置分:① 成员变量:在使用前,都经…

【实时数仓】DWM层订单宽表之维表关联异步查询(续)、DWM层支付宽表需求分析、需求实现(源码)

文章目录一 DWM层-订单宽表1 维表关联代码实现(1)优化2:异步查询a 关联省市维度b 关联SKU维度c 关联SPU维度d 关联品类维度e 关联品牌维度f 最终结果展示(2)结果写入kafka sink二 DWM层-支付宽表1 需求分析与思路2 需求…

界面控件DevExpress WinForm v22.1——拥有全新的WXI调色板

DevExpress WinForm拥有180组件和UI库,能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForm能完美构建流畅、美观且易于使用的应用程序,无论是Office风格的界面,还是分析处理大批量的业务数据,它都能轻松胜任…

车载以太网解决方案,你知多少?

近年来,为了满足智能网联汽车的开发要求,车载以太网技术开始逐渐进入人们的视野。而以太网技术已经成为下一代车载络架构的趋势之一,其发展之迅猛,使得各主机厂纷纷产生了浓厚的兴趣并投入研发。 一 为什么使用车载以太网 | 对高…

UE4 GIS Cesium for Unreal插件使用

第一步:安装Cesium for Unreal插件 如果尚未安装,请先安装Cesium for Unreal插件。 在虚幻引擎市场上打开Cesium for Unreal插件页面。2. 登录虚幻引擎商城,并单击免费按钮,将插件安装在虚幻引擎中。 第二步:创建项…

这里有 10 个省时间的 PyCharm 技巧

0. PyCharm 常用快捷键 1. 查看使用库源码 经常听人说,多看源码。源码不仅能帮我们搞清楚运行机制,还能学习优秀的库或者框架的最佳实践。 调用库时,你可以在你好奇的几乎任何地方点击 CommandB,就可以很方便的跳转到源码里的类&…