JS中的闭包和上下文

news2025/3/13 17:20:20

在这里插入图片描述

变量提升 和 函数提升

这里要提到一个提升的概念,即在JS中,在解析代码之前还有一个预处理的过程,这个过程中会把部分变量和函数声明提前到代码的最顶部,
会在其他所有代码之前执行。虽然当我们按照规范(严格模式或者TS等良好的编码习惯)时并不会因为提升而发生意料之外的情况,不过作为原生
JS中的特性,我们还是需要了解一下。

函数提升

还记得在C语言中,我们往往会把函数签名的声明提前都放到文件的最前面,然后实现放在main函数后面吗?这就是因为函数要先声明后使用,而在
JS中,解析器已经帮我们做好了这件事情,所以我们在使用函数的时候不需要提前声明它,解析器会帮我们自动把函数声明提前到代码的最顶部。不过
需要说明的是这种提升只对声明后的函数有效(比如这种形式:function xxxx()),而对于函数表达式(let fun = function(){})则不会有提升效果。

变量提升

当我们使用var关键字声明变量的时候就会产生变量提升的效果,这带来的效果就是即使我们先使用某个变量然后再声明它也不会报错,
因为变量声明会被提前到代码的最顶部:

console.log(nameless);
var nameless = 'hello';

这样写是不会报错的,但是会打印出undefined,因为虽然声明提前来但是赋值并没有提前,所以在声明复制之前nameless的值就是undefined了。

这里还有一种情况,那就是我们同时声明了一个变量和一个函数,且他们的名称相同,这个时候只会提升他们的其中之一,并且函数的提升优先级是高于
变量的提升的
。不过现在我们应该遇不到这种情况了,因为这会在控制台中输出错误提醒我们。

建议

总而言之,建议我们还是使用ES6中的letconst关键字来声明变量和常量,因为这样可以避免变量提升带来的问题。最好的情况是直接使用
TypeScript,它提供的编译阶段检查可以帮助我们避免一些错误。

执行期上下文 和 this的指向

所谓执行期上下文,这个函数执行时的环境。在那些面向对象的编程语言中,我们可以把函数在哪里被定义的类的实例作为上下文来看,我觉得在
JS中也可以这样类比,不过不同的是JS中类的存在感比较低,常常一个函数被定义在一个object中,可以把他视作是一个匿名类的实例。

比如说我们有这么一个函数:

function anchor() {
    let count = 0;
    return {
        add: function () {
            console.log(`add anchor:${this.count++}`)
        },
        remove: function () {
            console.log(`remove anchor${this.count--}`)
        },
        printInfo: function () {
            console.log(`anchor count:${this}`)
        }
    }
}

const anchorInstance = anchor();
anchorInstance.add();
anchorInstance.add();
anchorInstance.printInfo();

这个函数会返回一个对象,这个对象有三个方法,分别是增加this的count、减少this的count和打印this的指向。
当我们获得这个对象直接调用add或remove方法时:

add anchor:NaN
add anchor:NaN
anchor count:[object Object]

可以看到count的值是不正确的,我们更直观一点,直接打印count的值的话可以发现打印出来的是 undefined ,虽然我们在anchor函数中定义了count,
但是this.count并不会指向它,它指向的是所在对象的count,而所在对象中又没有定义count,所以就打印不出来了。当我们给这个对象加上count属性
后就可以正常打印了:

//调用
anchorInstance.count = 0;
anchorInstance.add();
anchorInstance.add();
anchorInstance.printInfo();
//结果:
// add anchor:0
// add anchor:1
// anchor count:[object Object]

这里我们再改动一下,把this关键字去掉结果发现也能正确打印信息。它正确使用到了anchor函数的局部变量,这实际上就产生了函数闭包。第二个原因就
是函数的作用域链,在嵌套的函数之中,变量会从内到外逐层寻找它的定义(就近原则),通过这种原则来决定取哪个值。当我们没有指定count的所在时,就会
根据作用域链向外寻找count变量。

除此之外,还需要注意的是关于箭头函数的this指向,匿名函数的this指向的是他的调用者,而箭头函数的this指向的是定义时寻找到的变量。这在
我们设置了原型的时候需要格外注意。

函数闭包

所谓闭包的意思就是函数内部的局部变量被外部持有了,类似于JVM中的可达性算法,由于这个变量被外部持有,也就是说正在被外部使用(不考虑内存泄漏)
那系统肯定不能把这个变量销毁,从而延长了函数局部变量的生命。

闭包在JavaScript中也有他的应用场景:

  • 数据封装和私有化
  • 作为函数工厂
  • 保留/追踪 函数的执行信息
  • 异步编程中(同步方式写异步)

上面的anchor函数中,我们使用的就是第一个应用场景,我们把count变量封装在了函数内部,外部只能通过return的接口来操作这个变量。

除此之外我觉得比较有用的就是追踪函数的执行信息了,比如我们可以封装这么一个函数:

let SecurityReporter = function() {
    let invokedInfos = [];
    return {
        connectDatabase() {
            const invokeInfo = {
                invokedTime : new Date(),
                invokedMethod : "[connect Database]"
            }
            invokedInfos.push(invokeInfo);
        },
        disconnectDatabase() {
            const invokeInfo = {
                invokedTime : new Date(),
                invokedMethod : "[disconnect Database]"
            }
            invokedInfos.push(invokeInfo);
        },
        reportInfos() {
            console.log(invokedInfos);
        }
    };
}

const securityReporterIns = SecurityReporter();
securityReporterIns.connectDatabase();
securityReporterIns.disconnectDatabase();
securityReporterIns.reportInfos();

我们构建了一个类似于埋点上报的系统,当我们调用方法时就会自动把相关信息存储到一个闭包中,方便我们整理和排查日志。

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

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

相关文章

GitLab 将停止为中国区用户提供服务,60天迁移期如何应对? | LeetTalk Daily

“LeetTalk Daily”,每日科技前沿,由LeetTools AI精心筛选,为您带来最新鲜、最具洞察力的科技新闻。 GitLab作为一个广受欢迎的开源代码托管平台,近期宣布将停止服务中国大陆、澳门和香港地区的用户提供服务。根据官方通知&#x…

【2024年最新】BilibiliB站视频动态评论爬虫

废话不多说,直接先放git仓库:GitHub - linyuye/Bilibili_crawler: bilibili爬虫,基于selenium获取oid与cookie,request获取api内容 〇:概念简述 oid:视频/动态的uuid,b站对于发布内容的通用唯…

汽车IVI中控开发入门及进阶(46):FFmpeg

概述: FFmpeg 是领先的多媒体框架,能够解码、编码、 转码、复用、解复用、流、过滤和播放 几乎所有人类和机器创建的东西。它支持最模糊的古老格式,直到最前沿。无论它们是由某个标准委员会、社区还是公司设计的。它还具有高度的可移植性:FFmpeg 在各种构建环境、机器架构…

计算属性 简写和 完整写法

计算属性渲染不加上括号 methods方法和computed属性区别: computed只计算一次,然后缓存,后续直接拿出来使用,而methods每次使用每次计算,不会缓存 计算属性完整写法: 既获取又设置 slice 截取 成绩案例 …

WebRTC Simulcast 大小流介绍与优化实践

Simulcast 是 WebRTC 中的一种标准化技术 ,简称大小流。通过 Simulcast,客户端可以同时发送同一视频的多个版本。每个版本都以不同的分辨率和帧率独立编码,带宽较多的拉流端可以接收较高质量的视频流,带宽有限的拉流端则可以接收较…

kong网关使用pre-function插件,改写接口的返回数据

一、背景 kong作为api网关,除了反向代理后端服务外,还可对接口进行预处理。 比如本文提及的一个小功能,根据http header某个字段的值,等于多少的时候,返回一个固定的报文。 使用到的kong插件是pre-function。 除了上…

轮播图带详情插件、uniApp插件

超级好用的轮播图 介绍访问地址参数介绍使用方法(简单使用,参数结构点击链接查看详情)图片展示 介绍 带有底部物品介绍以及价格的轮播图组件,持续维护,uniApp插件,直接下载填充数据就可以在项目里面使用 …

Vite内网ip访问,两种配置方式和修改端口号教程

目录 问题 两种解决方式 结果 总结 preview.host preview.port 问题 使用vite运行项目的时候,控制台会只出现127.0.0.1(localhost)本地地址访问项目。不可以通过公司内网ip访问,其他团队成员无法访问,这是因为没…

老旧小区用电安全保护装置#限流式防火保护器参数介绍#

摘要 随着居民住宅区用电负荷的增加,用电安全问题日益突出,火灾隐患频繁发生。防火限流式保护器作为一种新型电气安全设备,能够有效预防因电气故障引发的火灾事故。本文介绍了防火限流式保护器的工作原理、技术特点及其在居民住宅区用电系统…

Ftrans数据摆渡系统 搭建安全便捷跨网文件传输通道

一、专业数据摆渡系统对企业的意义 专业的数据摆渡系统对企业具有重要意义,主要体现在以下几个方面‌: 1、‌数据安全性‌:数据摆渡系统通过加密传输、访问控制和审计日志等功能,确保数据在传输和存储过程中的安全性。 2、‌高…

LabVIEW生物医学信号虚拟实验平台

介绍了一款基于LabVIEW的多功能生物医学信号处理实验平台的设计和实现。平台通过实践活动加强学生对理论的理解和应用能力,特别是在心电图(ECG)和脑电图(EEG)的信号处理方面。实验平台包括信号的滤波、特征提取和频谱分析等功能,能直观体验和掌握生物医学…

大数据实验三

Python and anaconda 实验三数据预处理和轨迹聚类参考地址: https://www.hifleet.com/wp/communities/data/hangyundashujujishukechengshiyanzhinanshujuyuchulijiguijijuleichixugengxinzhong#post-2212https://www.hifleet.com/wp/communities/data/hangyundas…

【python因果库实战14】因果生存分析3

标准化生存分析 参见《因果推断》一书第17.5节(“参数化的g公式”)。 在参数化标准化中,也称为“参数化g公式”,时间步k处的生存率是对协变量X水平和处理分配a条件下的条件生存率的加权平均,权重为每个分层中个体的比…

云边端一体化架构

云边端一体化架构是一种将云计算、边缘计算和终端设备相结合的分布式计算模型。该架构旨在通过优化资源分配和数据处理流程,提供更高效、更低延迟的服务体验。 下面是对这个架构的简要说明: 01云计算(Cloud Computing) — 作为中心…

C/C++ 数据结构与算法【哈夫曼树】 哈夫曼树详细解析【日常学习,考研必备】带图+详细代码

哈夫曼树(最优二叉树) 1)基础概念 **路径:**从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。 **结点的路径长度:**两结点间路径上的分支数。 **树的路径长度:**从树根到每一个结点的路径…

2、C#基于.net framework的应用开发实战编程 - 设计(二、三) - 编程手把手系列文章...

二、设计; 二.三、构建数据库; 此例子使用的是SQLite数据库,所以数据库工具用的SQLiteStudio x64,这个是SQLite专用的数据库设计管理工具,其它的数据库管理工具比如DBeaver的使用请见实战工具系列文章。 1、…

Edge SCDN酷盾安全重塑高效安全内容分发新生态

在数字化浪潮不断推进的今天,互联网内容的分发效率与安全性已成为企业业务发展的关键要素。酷盾安全推出的Edge Secure Content Delivery Network(Edge SCDN),不仅集成了分布式DDoS防护、CC防护、WAF防护及BOT行为智能分析等安全加…

JAVA HTTP压缩数据

/*** 压缩数据包** param code* param data* param resp* throws IOException*/protected void writeZipResult(int code, Object data, HttpServletResponse resp) throws IOException {resp.setHeader("Content-Encoding", "gzip");// write到客户端resp…

工厂+策略模式之最佳实践(疾病报卡维护模块API设计)

目录 💻业务场景 🔧应用技术 ⚙概要流程 ❗开发注意 服务类上标注了 自定义注解 却无法直接利用getDeclaredAnnotation 获取 *Spring代理机制 代理机制的工作原理 代理的工作机制 代理的使用场景 已获取EmrXXXServiceImpl 的Class,如…

帧缓存的分配

帧缓存实际上就是一块内存。在 Android 系统中分配与回收帧缓存,使用的是一个叫 ION 的内核模块,App 使用 ioctl 系统调用后,会在内核内存中分配一块符合要求的内存,用户态会拿到一个 fd(有的地方也称之为 handle&…