解决闭包+for+function的问题

"Hello World, Hello Blog"

Posted by wudimingwo on December 15, 2018
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function test() {
        var arr = [];
        for(var i = 1;i <= 10;i++){
          arr[i] = function () {
          	console.log(i);
          }
        }
        return arr;
      }
      
      var arr = test();
      arr[7]();//11
      我们期望能打印7,但实际打印的是11

我们把for循环拆开
function test(){
var arr = [];
i = 1;
arr[1] = function(){console.log(i)}
i = 2;
arr[2] = function(){console.log(i)}
.......
arr[10] = function(){console.log(i)}
i = 11;
根据闭包的知识,我们可以知道,这十个函数,访问的都是 i = 11;

} 

解决办法 版本1.0 用闭包解决闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
 function test() {
        var arr = [];
        for(var i = 1;i <= 10;i++){
        (function (j) {
          arr[j] = function () {
            console.log(j);
           }
          })(i);
        }
        return arr;
      }
      
      var arr = test();
      arr[7]();//输出7



解决了, 这是为什么呢?
我们拆一下for
 function test() {
        var arr = [];
        i = 1;
        (function (j) {//这是立即执行的
          arr[j] = function () {
            console.log(j);//本函数内没有,就找最近AO里的j//而j不是 test AO里的变量
            //每个函数的j都在各自的AO里,所以互不影响.
            //因为立即执行,j又会被i赋值;不会受到i的变化.
            //第一次接触学习的时候,我也很懵逼,习惯了就好理解了.
           }
          })(i);
        }
        i = 2;
        (function (j) {
          arr[j] = function () {
            console.log(j);
           }
          })(i);
        }
        ....
        i=10
        (function (j) {
          arr[j] = function () {
            console.log(j);
           }
          })(i);
        }
        i = 11

        return arr;
      }
      
      var arr = test();
      arr[7]();

版本2.0 forEach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
bug版1.0
      function test() {
        var arr = Array(10);//第一个没想到的是,空数组forEach不循环;
        arr.forEach(function (item, index) {
          item = function () {
          	console.log(index);
          }
        })
        return arr;
      }
      
      var arr = test();
      arr[7]();

bug版1.1
      function test() {
        var arr = [1,1,1,1,1,1,1,1,1,1];//这个样子挺蠢的,哈哈
        arr.forEach(function (item, index) {
          item = function () {//问题出在item,我赋值给item并不能影响arr,即使我改成arr = [[],...,[]];
          	console.log(index);
          }
        })
        return arr;
      }
      
      var arr = test();
      arr[7]();

修补bug版1.2
function test() {
        var arr = [1,1,1,1,1,1,1,1,1,1];
        arr.forEach(function (item, index) {
          console.log(item);
          arr[index] = function () {
          	console.log(index);
          }
        })
        return arr;
      }
      
      var arr = test();
      arr[7]();


由于实在不理解为什么arr = Array(10); forEach根本就不执行,一次循环都不执行
所以我用模拟的forEach 试了一下

Array.prototype.myForEach = function (fn,context) {
        var context = context || window;
      	var arr = this;
      	var len = arr.length;
      	for(var i = 0;i < len;i++){
      	  fn.call(context,arr[i],i,arr);
      	}
      }
      function test() {
        var arr = Array(10);
        arr.myForEach(function (item, index) {
          console.log(item);
          arr[index] = function () {
          	console.log(index);
          }
        })
        return arr;
      }
      
      var arr = test();
      arr[7]();//输出 7

这个测试有个很明显的结论,模拟写的myForEach 肯定和原生 forEach 有区别.


在这个案例上,用forEach感觉很蠢,我也没想到,
主要是因为,这个案例上,我们主要是赋值操作.
如果取值操作的话,很多时候能很自然的解决闭包问题.

为什么forEach 能解决问题? 看这一段

for(var i = 0;i < len;i++){
      	  fn.call(context,arr[i],i,arr);
 }
这里for循环里面fn() 是执行形态(应该叫表达式),所以不受影响,
自己可以再想想.

版本3.0 ES6 let 取代var

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
关于let 和ES6的更多知识可以自行百度

function test () {
  var arr = [];
  for(let i = 1; i <= 10;i++ ){
  arr[i] = function () {console.log(i)}
}
return arr;
}
var arr = test();
arr[7]();

let 是块级作用域,
这里的关键是 for(let i) 括号里是一个单独的块级作用域.
其实就算这样也不太好理解.


我们拆开for

function test () {
var arr = []
{let i=1
  {
  arr[i] = function () {console.log(i)}
  }
}

{let i=2
  {
  arr[i] = function () {console.log(i)}
  }
}  
}
....
{let i=10
  {
  arr[i] = function () {console.log(i)}
  }
}
只能这么理解,但我也觉得有地方说不通. 如果每次生成一个块级重新声明 let i,
那么每个i都是独立的,应该没有关系,但很明显应该是同一个i ,起码后一个i受到前一个i的影响.

如果是同一个i,那么就算独立拥有一个块级,也应该无法满足需求.
比如这样拆for
{
let i=0;
  {
    arr[i] = function () {console.log(i)}
  }
i = 1;
{
    arr[i] = function () {console.log(i)}
  }
...
i = 10;
{
    arr[i] = function () {console.log(i)}
  }
i = 11
} 
如果是这样的话,应该达不到想要的效果.


当然了,我们只需要知道 for + let的时候可以解决这个问题就行了.