面试官:JavaScript执行机制中的闭包?

server/2024/9/23 20:06:21/

前言

JavaScript 中的闭包指的是一个函数以及其捆绑的周边环境状态的引用的组合。闭包可以让开发者从内部函数访问外部函数的作用域,即使外部函数已经执行完毕

今天我们通过JavaScript执行机制来聊聊闭包

正文

首先来分析这段代码的执行机制,这段代码我们主要了解作用域链,以及函数的外部作用域outer

outer是根据词法作用域规则生成的

function bar() {console.log(myName);
}
function foo() {var myName = 'Tom'bar()
}var myName = 'Jerry'
foo()

在上述代码中,执行 foo 函数时,在 bar 函数内部打印 myName 时,会查找变量 myName

由于 bar 函数内部没有定义 myName ,它会沿着作用域链向上查找。首先在 foo 函数的作用域中查找,foo 函数中定义了 myName'Tom' ,但是 bar 函数调用时,它无法直接访问 foo 函数作用域内的 myName

接着继续向上查找,在全局作用域中找到了定义的 myName'Jerry' ,所以最终打印的是全局作用域中的 myName ,即 'Jerry'

我们来画一下这个的执行流程

image.png

  • 调用栈中创建全局上下文执行对象(变量、词法环境)里面有bar=x001指向堆里面(非原始数据类型存放地)foo myName
  • myName最开始是undefined,全局预编译结束,全局执行,myName=Jerry 调用foo
  • 对foo进行预编译,创建AO对象(foo的执行上下文对象)有变量词法环境,myName=undefined,foo里面代码开始执行,
  • myName=Tom,foo变量环境中还有一个outer指针,值指向全局上下文执行对象,为什么指向全局?
  • outer指向谁取决于foo函数所在的词法作用域在哪里(foo声明在哪里),foo在全局作用域中,所以outer指向全局上下文执行对象
  • 这个指向过程就是作用域链,bar开始调用,创建bar执行上下文,入栈,有变量、词法环境,
  • 变量环境中没有声明东西,但是有outer指针,指向全局上下文对象。
  • 然后开始找myName,自己bar中没有找到,然后顺着outer往全局找,找到了jerry因此打印jerry

这里我们还没具体讲解闭包

接下来我们分析这段代码

function foo() {function bar() {var age = 18console.log(myName);}var myName = 'Tom'return bar
}
var myName = 'jerry'
var fn = foo()
fn()

在上述代码中,执行 fn() 时,在 bar 函数内部打印 myName ,首先在 bar 函数内部查找 myName 变量,未找到。

然后沿着作用域链向上查找,在 foo 函数的作用域中找到了定义的 myName'Tom'

而全局作用域中定义的 myName'jerry' ,但由于作用域链的查找顺序,优先使用了 foo 函数作用域中的 myName ,所以最终打印的是 'Tom'

这我们同样分析一下预编译过程

image.png

这里我们就要引出闭包了,当bar执行完了,就得被销户,然后就是foo了,可是这里就出现了一个问题,foo返回了一个方法barbarouter指向的foo,但是foo就被销毁了,可是在全局调用了bar,bar是需要使用到foo里的参数的,于是就还是销毁了foo,但是留下了bar需要的参数,以及他的outer,形成了小书包,这就是闭包

image.png

闭包有许多的好处:

  1. 实现数据私有化和封装
    通过闭包,可以将一些变量和函数隐藏在内部函数中,外部无法直接访问和修改,从而实现了一定程度的封装和数据保护。
  2. 保持函数执行的上下文和状态
    闭包能够让函数记住其创建时的环境状态,即使外部函数已经执行完毕,内部函数仍然可以访问和操作这些状态信息。这对于实现需要保持状态的功能非常有用,比如计数器、缓存等。
  3. 模拟私有方法
    在 JavaScript 中没有真正的私有方法,但可以利用闭包来模拟私有方法,只在特定的函数内部可访问和操作。
  4. 函数柯里化和偏函数应用
    闭包有助于实现函数柯里化和偏函数应用,使函数的参数传递更加灵活和可定制。
  5. 模块模式
    可以使用闭包创建模块,模块中的变量和方法只在模块内部可访问,对外只暴露必要的接口。

那么让我们分析一下这段代码

function add() {let count = 0;function fn() {count++return count;}return fn;
}
var res = add();
console.log(res());
console.log(res());
console.log(res());

add函数的调用是不依赖全局变量的,封装函数 实现累加

add 函数返回了内部函数 fn

当执行 var res = add(); 时,res 接收到了 fn 函数,并且 fn 函数形成了一个闭包,能够访问到 add 函数中的 count 变量。

第一次执行 console.log(res()); 时,count 自增为 1 并返回,所以打印 1

第二次执行 console.log(res()); 时,count 再次自增为 2 并返回,所以打印 2

第三次执行 console.log(res()); 时,count 继续自增为 3 并返回,所以打印 3

总结

本文讲解了JavaScript的执行机制和JavaScript执行机制中的闭包,相信看到这里的你一定会有收获的!!!!


http://www.ppmy.cn/server/51099.html

相关文章

如何解决跨境传输常见的安全及效率问题?

在当今全球化的商业版图中,企业为了拓展国际市场和增强竞争力,跨境传输数据已成为一项不可或缺的业务活动。合格的数据跨境传输方案,应考虑以下要素: 法律合规性:确保方案符合所有相关国家的数据保护法律和国际法规&am…

LeetCode 3186 最大施法伤害

题目信息 LeetoCode地址: . - 力扣(LeetCode) 题目理解 这道题很直观,玩游戏的都懂,伤害最大化嘛! 但是每个法术释放与否可能会影响总体的伤害,因此是从局部最优解找到全局最优解的动态规划问题&#x…

34.构建核心注入代码

上一个内容:33.获取入口点 以 33.获取入口点 它的代码为基础进行修改 实现的功能是把LoadLibrary函数注入到目标进程实现加载我们的模块。LoadLibrary只有有程序使用过了它的代码就会加载到内存中(因为动态链接库是内存加载)就是a程序要用L…

VOC数据集

VOC(Visual Object Classes)格式的数据集是一种用于计算机视觉任务的标准数据集格式,它最初是由Pascal VOC(PASCAL Visual Object Classes)数据集引入的。VOC数据集格式定义了一套标准化的数据集结构,包括X…

Java热部署:让应用更新如丝般顺滑,告别繁琐重启!

目录 手动启动热部署 自动启动热部署 参与热部署监控的文件范围配置 关闭热部署 什么是热部署?简单说就是你程序改了,现在要重新启动服务器,嫌麻烦?不用重启,服务器会自己悄悄的把更新后的程序给重新加载一遍&…

WordPress插件数据库批量替换内容工具插件

1、安装插件后,我们就可以在后台菜单看到工具操作界面 2、目前支持网站内容、标题、评论指定字符的快速替换 3、可以快速解决以往我们需要从MYSQL数据库命令替换的烦恼

pydictor 字典工具的使用指南

在渗透测试和安全审计过程中,字典攻击是一种常见的密码破解手段。一个强大且灵活的字典生成工具可以显著提高破解效率。本文将详细介绍如何使用 pydictor,这是一个功能丰富的字典生成工具,广泛用于生成定制化的密码字典。 pydictor 简介 py…

【代码详解】点云融合dypcd方法

MVS深度图估计之后,需要融合成点云提交给benchmark来评测,而融合方式和实现版本多种多样,参数也丰俭由人,所以一直是trick高发区。今天来详解一下其中的dypcd融合。 该方法最初应该是由D2HC-RMVSNet提出,这篇文章是EC…