javascript - 试图理解范围和闭包
问题描述
我已经通过一些教程来了解 JavaScript 中的作用域和闭包,并遇到了下面的代码。
我理解输出为 5,5,5,5,5 的第一个块,因为该函数在 for 循环完成后执行。但是我不完全理解为什么第二个块起作用......我是否认为在每次迭代中都会调用一个新函数,所以内存中有 5 个函数同时运行?我想要一个简单易懂的解释 - 我是学习 JavaScript 的新手。
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log('index: ' + i);
}, 1000);
}
for (var i = 0; i < 5; i++) {
(function logIndex(index) {
setTimeout(function () {
console.log('index: ' + index);
}, 1000);
})(i)
}
解决方案
由于您在下面的评论,重构了我的答案。
在开始之前,我们需要解决几个术语:
执行上下文——简单来说,这是一个函数在其中执行的“环境”。例如,当我们的应用程序启动时,我们在“全局”执行上下文中运行,当我们调用一个函数时,我们创建一个新的执行上下文(嵌套在全球范围内)。
每个执行上下文都有一个变量环境(范围),当然还有函数体(它是“命令”)。调用堆栈- 跟踪我们在哪个执行上下文,以及哪些变量可供我们使用,每个执行上下文被推送到调用堆栈,当函数返回时,它从调用堆栈和它的环境中弹出被标记为垃圾回收(释放内存),除了我们稍后会发现的一个异常。
Web 浏览器 API 和事件循环- JavaScript 是单线程的(为了简单起见,我们称它为线程),但有时我们需要处理异步操作,例如点击事件、xhr 和计时器。
浏览器通过它的 API、addEventListener
、XHR
/fetch
等公开它们setTimeout
......
这里很酷的是它会在与 javascript 线程不同的线程上运行它。但是浏览器如何在主线程上运行我们的代码呢?通过我们提供给它的回调(就像您对 所做的那样setTimeout
)。
好的,它什么时候会运行我们的代码?我们想要一种可预测的方式来运行我们的代码。
进入事件循环和回调队列,浏览器将每个回调推送到这个队列(promise 到另一个具有更高优先级的队列)并且事件循环正在监视调用堆栈,当调用堆栈时是空的,并且没有更多的代码可以在全局中运行,事件循环将获取下一个回调并将其推送到调用堆栈。闭包——简单来说,就是当一个函数访问它的词法(静态)范围时,即使它在它之外运行。稍后会更清楚。
在示例 #1 - 我们在全局执行上下文中运行一个循环,创建一个变量i
并在每次迭代时将其更改为一个新值,同时将 5 个回调传递给浏览器(通过setTimeout
API)。
事件循环不能将这些回调推回调用堆栈,因为它还不是空的。当循环结束时,调用堆栈为空,事件循环将我们的回调推送给它,每个回调访问i
并打印最新的值5
(关闭,我们i
在它应该被销毁后访问环境)。原因是所有回调都是在相同的执行上下文中创建的,因此它们引用相同的i
.
在示例 #2 - 我们在全局执行上下文上运行一个循环,在每次迭代中创建一个新函数 ( IIFE ),从而创建一个新的执行上下文。i
这将在此执行上下文中创建副本,而不是像以前那样在全局上下文中创建副本。在这个执行上下文中,我们通过 发送回调setTimeout
,就像在事件循环等待循环完成之前一样,因此调用堆栈将为空并将下一个回调推送到堆栈。但是现在当回调运行时,它会访问创建它的执行上下文并打印i
全局上下文从未更改的执行上下文。
所以基本上我们有 5 个执行上下文(没有全局),每个都有自己的i
.
希望现在更清楚了。
我真的建议观看有关事件循环的视频。
推荐阅读
- encryption - HTTPS 通信安全
- python - Python Matplotlib 按钮小部件和 SpanSelector 事件
- java - 使用一个或多个堆栈实现线程安全队列 - java
- scala - 通过处理 null 来 Spark Scala 逐行平均
- php - 安装 mysqlnd_qc 插件
- reactjs - React 组件渲染本地化文本(道具)
- quantum-computing - 在 LiH 模拟中找不到 QDK 文件错误
- angular - Angular6路由器导航到共享相同组件的多个子路由
- java - 如何在房间数据库查询中使用预定义的字符串?
- android - 如何知道具有给定意图操作的特定 apk 可以通过 am 传递哪些参数