循环与闭包 之 for循环经典问题解释 / 结合《你不知道的JS》与《高程》案例

原创 2017年07月03日 13:44:34

案例一

for (var i = 1; i <= 5; i++) {
  setTimeout( function timer() {
    console.log(i);
  }, i*1000)
}

输出结果:

  • 当时间是固定的数,如0、1000、6000,执行结果就是0、1、6秒后,一次输出五个6;
  • 当时间是 i*1000, 输出是:每隔1秒,输出一个6,共5次。

代码中到底有什么‘缺陷’,导致它的行为与 语义暗示的不一致呢?

缺陷 是 我们试图假设
循环中的每一个迭代在运行时,都会给自己’捕获’一个i的副本。但是,根据作用域的工作原理,实际情况是,尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。

这样说的话,当然所以函数共享一个 i 的引用。
循环结构让我们误以为背后还有更复杂的机制在起作用,实际上并没有。如果将延迟函数的回调重复定义5次,完全不使用循环,那它同这段代码是完全等价的。

                             ----《你不知道的JS 上卷》p49

show me the code

第一种情况

setTimeout()第二个参数是个常数

书中那段话就是说,

for (var i = 1; i <= 5; i++) {
  setTimeout( function timer() {
    console.log(i);
  }, 1000)
}

等价于

var i = 1
setTimeout( function timer() {
  console.log(i);
}, 1000)
var i = 2
setTimeout( function timer() {
  console.log(i);
}, 1000)
var i = 3
setTimeout( function timer() {
  console.log(i);
}, 1000)
var i = 4
setTimeout( function timer() {
  console.log(i);
}, 1000)
var i = 5
setTimeout( function timer() {
  console.log(i);
}, 1000)
var i = 6

输出情况:
当时间是固定的数,如0、1000、6000,执行结果就是0、1、6秒后,一次输出五个6;

解释:

setTimeout()函数是立即执行的,而它的回调函数是一定时间后才执行的。
所以:

... 开始计时 ...
var i = 1
setTimeout(1秒后回调),
 i = 2
setTimeout(我也1秒后回调),
 i = 3
setTimeout(我也1秒后回调),
 i = 4
setTimeout(我也1秒后回调),
 i = 5
setTimeout(我也1秒后回调),
 i = 6

 ... 1秒钟过去了 ...

 执行回调,都输出i的值

 ... 结束 ...

由此,形成了 每个一秒后全部输出的效果

而等到这些回调执行时,i的值就是6

第二种情况

setTimeout()第二个参数含有变量

同上

for (var i = 1; i <= 5; i++) {
  setTimeout( function timer() {
    console.log(i);
  }, i*1000)
}

等价于

var i = 1
setTimeout( function timer() {
  console.log(i);
}, i*1000)
var i = 2
setTimeout( function timer() {
  console.log(i);
}, i*1000)
var i = 3
setTimeout( function timer() {
  console.log(i);
}, i*1000)
var i = 4
setTimeout( function timer() {
  console.log(i);
}, i*1000)
var i = 5
setTimeout( function timer() {
  console.log(i);
}, i*1000)
var i = 6

输出情况:
当时间是 i*1000, 输出是:每隔1秒,输出一个6,共5次。

解释

setTimeout()函数是立即执行的,而它的回调函数是一定时间后才执行的。

所以:

... 开始计时 ...
var i = 1
setTimeout(1秒后回调),
 i = 2
setTimeout(2秒后回调),
 i = 3
setTimeout(3秒后回调),
 i = 4
setTimeout(4秒后回调),
 i = 5
setTimeout(5秒后回调),
 i = 6

 ... 1秒钟过去了 ...
 第一个回调,输出i的值

 ... 又一秒钟过去了...
 第二个回调,输出i的值 

 ...

 第五个回调,输出i的值
 ... 结束 ...

由此,形成了 每个一秒一个输出的效果

而等到这些回调执行时,i的值就是6

HOW TO FIX IT

我知道,加上闭包!

for (var i = 1; i <= 5; i++) {
  (function() {
    setTimeout( function timer() {
      console.log(i);
    }, i*1000)
  })()
}

然并卵,还是一秒一个6,五次

因为,作用域是空的。回调函数还是引用那个唯一的全局变量i。

正确的闭包姿势:

通过在闭包作用域中添加自己的变量,从而在每次迭代中,捕获i的副本。

for (var i = 1; i <= 5; i++) {
  (function() {
    var j = i
    setTimeout( function timer() {
      console.log(j);
    }, j*1000)         //至于时间这里,是i 是j无所谓
  })()
}

更简洁的姿势:

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout( function timer() {
      console.log(j);
    }, j*1000)
  })(i) 
}

由此,能够输出:1 2 3 4 5,一秒一个

ES6的打开方式: 块作用域

for (var i = 1; i <= 5; i++) {

    let j = i
    setTimeout( function timer() {
      console.log(j);
    }, j*1000)

}

或直接在for循环头部里,每次迭代都声明一次

for (let i = 1; i <= 5; i++) {

    setTimeout( function timer() {
      console.log(i);
    }, i*1000)

}

案例2

《JavaScript高级程序设计第三版》 p183

var a = function ceateFunctions() {
  var result = new Array();

  for (var i = 0; i < 10; i++) {
    result[i] = function () {
      return i
    }
  }

  return result
}
console.log(a()[0]());
//0 - 9 输出都是10  

道理一样。

都是因为引用同一个i,且没能在迭代中捕获i的副本,或者没能在迭代中及时按当时的值执行。直到i早都变成10了,才执行,RHS引用的结果当然是i此刻的值,即10。

版权声明:本文为博主原创文章,未经博主允许不得转载。

js经典闭包问题

首先,我们经常要用到点击一个下拉列表、li元素集某一项获取该项在整个下拉列表、li元素集的下标;这个看似简单的问题,初学者往往会有许困惑。 这里我们以单选按钮为例,场情:点击相应单选按钮出现该按钮下标...
  • huang100qi
  • huang100qi
  • 2013年07月03日 16:54
  • 2728

JS之经典for循环闭包问题解决方法

JS之经典for循环闭包问题解决方法 像这样一个代码片段,初学者会理所当然地认为依次点击Li会弹出相应的0、1、2、3、4、5,但实际结果却是这样的 我们无论点哪个按钮,最后弹出来的都是6。...
  • YuLi_Zoe
  • YuLi_Zoe
  • 2015年01月30日 13:09
  • 6931

for循环中的闭包问题及解决方案

说到闭包,我们首先来看一个最最简单的例子,也是最最基础的例子:为多个相同的元素,绑定事件,在点击每一个元素时,提示被点击元素的排列位置。 栏目1 栏目2 ...
  • qq_34986769
  • qq_34986769
  • 2016年08月07日 19:51
  • 1448

循环与闭包 之 for循环经典问题解释 / 结合《你不知道的JS》与《高程》案例

案例一for (var i = 1; i
  • Beijiyang999
  • Beijiyang999
  • 2017年07月03日 13:44
  • 303

用9种办法解决 JS 闭包经典面试题之 for 循环取 i

闭包 正确的说,应该是指一个闭包域,每当声明了一个函数,它就产生了一个闭包域(可以解释为每个函数都有自己的函数栈),每个闭包域(Function 对象)都有一个 function scope...
  • qq_27289001
  • qq_27289001
  • 2016年09月01日 13:03
  • 1075

js for循环调用ajax 函数封装 闭包 回调

此次在编写代码时遇到for循环中发送ajax请求,遇到的问题是for循环完后,才执行ajax请求一次,通过网上查找资料,解决方法记录如下:       1.方法一: for(var i = 0; i...
  • followwwind
  • followwwind
  • 2017年08月31日 11:57
  • 345

js 解决js for 循环中的闭包问题

在js中想要取出for循环中的值一直都是很多新手的一大难题,正好近期做过一些这样的案例,在这里大言不惭的讲解下 例一: var arrlist=document.querySelectorAll('l...
  • z_572712675
  • z_572712675
  • 2017年05月06日 17:44
  • 212

闭包与循环的尝试

闭包与循环的尝试for(var i =0; i
  • Dear_Mr
  • Dear_Mr
  • 2017年07月13日 21:49
  • 183

用9种办法解决 JS 闭包经典面试题之 for 循环取 i

闭包 正确的说,应该是指一个闭包域,每当声明了一个函数,它就产生了一个闭包域(可以解释为每个函数都有自己的函数栈),每个闭包域(Function 对象)都有一个 function scope...
  • u013243347
  • u013243347
  • 2016年08月06日 10:11
  • 2151

使用 let 解决for 循环闭包 i变量问题

当let块范围变量出现后,可以方便解决 for循环i变量绑定问题,demo 如下 let 解决for 循环闭包变量问题 ...
  • ISaiSai
  • ISaiSai
  • 2016年08月18日 09:52
  • 2092
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:循环与闭包 之 for循环经典问题解释 / 结合《你不知道的JS》与《高程》案例
举报原因:
原因补充:

(最多只允许输入30个字)