试图了解范围和闭包

时间:2018-06-02 12:52:51

标签: javascript function scope closures scoping

我已经通过一些教程来了解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)
}

4 个答案:

答案 0 :(得分:0)

是的,你是对的5函数将执行,而logIndex你不需要anonymous function来完成这类工作。

(funtion(index){}) =&gt; function定义

(funtion(index){})(i) =&gt;通过i调用function

for (var i = 0; i < 5; i++) {
  (function (index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}

答案 1 :(得分:0)

你的例子2,即:

for (var i = 0; i < 5; i++) {
  (function logIndex(index) {
    setTimeout(function () {
      console.log('index: ' + index);
    }, 1000);
  })(i)
}

工作正常,因为在示例中,您使用闭包为每个超时函数安排了“i”的不同副本。

您甚至可以使用let来实现它,请尝试以下操作:

for (let i = 0; i < 5; i++) {
    setTimeout(function () {
      console.log('index: ' + i);
    }, 1000);
}

这是有效的,因为在一个带有基于let的索引的循环中,循环中的每次迭代都会有一个新值i,其中每个值都在循环中作用域,因此你的代码可以按预期工作。

有关详细说明,请参阅:Reference

答案 2 :(得分:0)

根据您的评论重新构建我的答案。

在开始之前,我们需要解决几个术语:

  1. 执行上下文 - 简单来说,这是函数执行的“环境”。例如,当我们的应用程序启动时,我们运行“全局”执行上下文,当我们调用一个函数时,我们创建一个新的执行上下文(嵌套在全局内) 每个执行上下文都有一个变量环境(范围),当然还有函数体(它是“命令”)。

  2. 调用堆栈 - 为了跟踪我们在哪个执行上下文,以及哪些变量可用于我们,每个执行上下文都被推送到调用堆栈,当函数返回它从调用堆栈弹出,它的环境被标记为垃圾收集(释放内存),除了我们稍后会发现的一个例外。

  3. Web浏览器API和事件循环 - JavaScript是单线程的(让我们称之为一个简单的线程),但有时我们需要处理异步操作,例如click-events ,xhr和计时器。
    浏览器通过它的API公开它们,addEventListenerXHR / fetchsetTimeout等... 这里很酷的是它将在不同的线程上运行它,然后是javascript的线程。但是浏览器如何在主线程上运行我们的代码呢?通过我们提供给它的回调(就像你对setTimeout所做的那样) 好的,什么时候运行我们的代码?我们想要一种可预测的方式来运行我们的代码 进入事件循环和回调-Que,浏览器将每个回调推送到此que(顺便说一下,promises转到具有更高优先级的不同que),并且事件循环正在观看调用堆栈,当调用堆栈时是空的没有更多的代码可以在全局运行事件循环将获取下一个回调并将其推送到调用堆栈。

  4. 关闭 - 简单来说,就是当一个函数访问它的词法(静态)范围时,即使它在它之外运行。以后会更清楚。

  5. 在示例#1中 - 我们在全局执行上下文上运行循环,创建变量i并在每次迭代时将其更改为新值,同时将5个回调传递给浏览器(通过setTimeout API) 事件循环不能将这些回调推回到调用堆栈,因为它还不是空的。当循环完成时,调用堆栈为空,事件循环将回调推送给它,每个回调访问i并打印最新值5(关闭,我们访问{{ 1}}它被认为被破坏之后的环境方式)。这样做的原因是所有回调都是在同一个执行上下文中创建的,因此它们引用相同的i

    在示例#2中 - 我们在全局执行上下文上运行循环,在每次迭代时创建一个新函数(IIFE),从而创建一个新的执行上下文。这将在此执行上下文中创建i的副本,而不是像之前那样在全局上下文中创建。{1}}。在这个执行上下文中,我们通过i发送回调,就像事件循环等到循环结束之前一样,所以调用堆栈将为空并将下一个回调推送到堆栈。但现在当回调运行时,它会访问它创建它的执行上下文并打印从未被全局上下文更改的setTimeout

    所以基本上我们有5个执行上下文(没有全局),每个都有自己的i

    希望现在更清楚。

    我真的建议您观看有关事件循环的this视频。

答案 3 :(得分:0)

for (var i = 0; i < 5; i++) {
    (function logIndex(index) {
        setTimeout(function () { console.log(index); }, 1000); // 0 1 2 3 4
    })(i)
}

您的代码将在内部为每个迭代创建一个闭包,并与当前索引值绑定。因此,总的来说,您正在创建具有不同索引值的 5 闭包。

一旦setTimeout在每个闭包内部经过,它将打印其本地可见的索引值。