Javascript 试图理解范围和闭包
为了理解JavaScript中的作用域和闭包,我已经学习了一些教程,并了解了下面的代码 我理解第一个块,其中输出是5,5,5,5,5,因为函数在for循环完成后执行。但是我不完全理解为什么第二个块可以工作……我认为在每次迭代中都会调用一个新函数,所以内存中有5个函数同时运行,这对吗?请给我一个简单易懂的解释,我是JavaScript新手Javascript 试图理解范围和闭包,javascript,function,scope,closures,scoping,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('ind
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)
}
for(变量i=0;i<5;i++){
setTimeout(函数(){
console.log('index:'+i);
}, 1000);
}
对于(变量i=0;i<5;i++){
(函数logIndex(索引){
setTimeout(函数(){
console.log('索引:'+索引);
}, 1000);
})(一)
}
是的,您是对的,5个函数将执行,不需要logIndex
您可以使用匿名函数
进行此类工作
(函数(索引){})
=>函数
定义
(函数(索引){})(i)
=>通过传递i调用函数
for(变量i=0;i<5;i++){
(功能(索引){
setTimeout(函数(){
console.log('索引:'+索引);
}, 1000);
})(一)
}
您的示例2,即:
for (var i = 0; i < 5; i++) {
(function logIndex(index) {
setTimeout(function () {
console.log('index: ' + index);
}, 1000);
})(i)
}
for(变量i=0;i<5;i++){
(函数logIndex(索引){
setTimeout(函数(){
console.log('索引:'+索引);
}, 1000);
})(一)
}
工作正常,因为在本例中,您使用闭包为每个超时函数安排了“i”的不同副本
您甚至可以使用let来实现它,请尝试以下操作:
for(设i=0;i<5;i++){
setTimeout(函数(){
console.log('index:'+i);
}, 1000);
}
根据下面的评论重构我的答案
在开始之前,我们需要解决几个术语:
执行上下文-简单来说,这就是函数执行的“环境”。例如,当我们的应用程序启动时,我们在“全局”执行上下文上运行,当我们调用一个函数时,我们创建一个新的执行上下文(嵌套在全局上下文中)。
每个执行上下文都有一个可变环境(范围),当然还有函数体(它是“命令”)
调用堆栈-为了跟踪我们所处的执行上下文,以及哪些变量对我们可用,当函数返回从调用堆栈弹出的执行上下文时,每个执行上下文都被推送到调用堆栈,并且它的环境被标记为垃圾收集(释放内存),除了一个例外,我们稍后会发现
Web浏览器API和事件循环-JavaScript是单线程的(我们称之为简化线程),但有时我们需要处理异步操作,如单击事件、xhr和计时器。
浏览器通过其API、addEventListener
、XHR
/fetch
、setTimeout
等公开这些文件。
这里很酷的一点是,它将在不同的线程上运行它,然后在javascript的线程上运行。但是浏览器如何在主线程上运行我们的代码呢?通过我们提供给它的回调(就像您对setTimeout
所做的那样)。
好的,它什么时候运行我们的代码?我们需要一种可预测的方式来运行代码。
进入事件循环和回调Que,浏览器将每个回调推送到该Que(顺便说一句,它将转到具有更高优先级的不同Que),并且事件循环正在监视调用堆栈,当调用堆栈为空且全局中没有更多代码可运行时,事件循环将捕获下一个回调并将其推送到调用堆栈
闭包-简单地说,就是当函数访问其词法(静态)范围时,即使它运行在该范围之外。以后会更清楚
在示例#1中-我们在全局执行上下文上运行一个循环,创建一个变量i
,并在每次迭代中将其更改为一个新值,同时将5个回调传递给浏览器(通过setTimeout
API)。
事件循环无法将这些回调推回调用堆栈,因为它尚未为空。但是,当循环完成时,调用堆栈为空,事件循环将我们的回调推送到它,每个回调访问i
,并打印最新的值5
(闭包,在它被销毁后,我们访问i
环境)。原因是所有回调都是在相同的执行上下文上创建的,因此它们引用相同的i
在示例2中,我们在全局执行上下文上运行一个循环,在每次迭代中创建一个新函数(),从而创建一个新的执行上下文。这将在该执行上下文中创建i
的副本,而不是像以前那样在全局上下文中。在这个执行上下文中,我们通过setTimeout
发送回调,就像事件循环等待循环完成之前一样,因此调用堆栈将为空,并将下一个回调推送到堆栈中。但是现在,当回调运行时,它访问创建回调的执行上下文,并打印从未被全局上下文更改的i
因此,基本上我们有5个执行上下文(没有全局上下文),每个上下文都有自己的i
希望这一点现在更清楚
我真的建议观看关于事件循环的视频。for(var I=0;I<5;I++){
for (var i = 0; i < 5; i++) {
(function logIndex(index) {
setTimeout(function () { console.log(index); }, 1000); // 0 1 2 3 4
})(i)
}
(函数logIndex(索引){
setTimeout(函数(){console.log(index);},1000);//0 1 2 3 4
})(一)
}
您的代码将在内部创建一个与当前值绑定的闭包