JavaScript——由for循环引起的关于var和做用域的思考

由for循环引起的关于var和做用域的思考

Stage 1

  • 原由是在某技术博客里看到了以下代码
function fun(){
    for(var i=0; i<lis.length; i++){    //此处的length=5
      lis[i].onclick = function(){
        console.log(i);
      }
    }
}
复制代码

因而我在console里写入了如上代码,依次点击lis,输出了五次4,这对于写惯了c语言的我是一个观念上的颠覆,因而开始了大规模的资料查找,试图解决个人这个疑惑。html

Stage 2

  • 在通过几番询问和一些技术博客的翻阅以后,获得了以下的一种解释:

"在这个函数里面的i其实引用的是最后一次i的值,为何不是1,2,3,4...呢? 由于for循环中并无执行这个函数,这个函数是在你点击的时候才执行的,当执行这个函数的时候,它发现它本身没有这个变量i,因而向它的做用域链中查找这个变量i,由于当你单击这个box的时候已经for循环完了,因此找到的i是最后一次赋值后的i"es6

  • 本觉得事情到此结束了,可我感受仍是差了些什么,下面这篇博客解开了我心中的最别扭的结。 引用自:www.cnblogs.com/qieguo/p/54…
function createFunctions(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var funcs = createFunctions();
for (var i=0; i < funcs.length; i++){
    console.log(funcs[i]());
}
复制代码

陷阱就是:函数带()才是执行函数! 单纯的一句 var f = function() { alert('Hi'); }; 是不会弹窗的,后面接一句 f(); 才会执行函数内部的代码。上面代码翻译一下就是:bash

var result = new Array(), i;
result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
...
result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换!
i = 10;
funcs = result;
result = null;

console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10
console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10
...
console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10
复制代码

"为何只垃圾回收了 result,但却不收了 i 呢? 由于 i 还在被 function 引用着啊。比如一个餐厅,盘子老是有限的,因此服务员会去巡台回收空盘子,但还装着菜的盘子他怎么敢收? 固然,你本身手动倒掉了盘子里面的菜(=null),那盘子就会被收走了,这就是所谓的内存回收机制。"闭包

Stage 3

  • 在《JavaScript高级程序设计》的7.2节终于巩固了个人理解:

“做用域链的机制引出了一个值得注意的反作用,即闭包只能取得包含函数中任何变量的最后一个值。”函数

“表面上看,每一个函数都应该返回本身对应的i值,但实际上每一个函数都返回了同样的值。由于每一个函数的做用域链中都保存着fun()函数的活动对象,因此他们引用的都是同一个变量i。当fun()函数返回后,变量的i值是4,此时每一个函数都引用着保存变量i的同一个变量对象,因此在每一个函数内部i的值都是10。”ui

  • 想法:绑定的函数并非马上就实现,而是处于等待调用的状态。当程序的执行流进入一个函数的时候,这个函数被推入一个环境栈中,再进行变量读取和函数内容的实现。
  • 实例化地,在本篇开头的代码中,五次循环将lis[i].onclick事件分别绑定在了五个匿名函数上,开辟了五个执行环境,进而造成了五条做用域链,形如:[闭包]→[fun()的活动对象]→[全局变量对象],而很容易理解地,fun()活动对象是这五条做用域链所共享的,天然i值也就是共享的了

Stage 4

  • 这部分该讲讲解决方法了
  • 高程上推荐的方法:经过建立另外一个匿名函数强制让闭包行为符合预期
function fun(){
    for(var i=0; i<lis.length; i++){    //此处的length=5
      lis[i].onclick = (function(num){
          return function(){
            console.log(num);
          }
      })(i)
    }
}
复制代码

这种方法在每次循环中,用当即执行的匿名函数记录下了当前的i值(num),并建立了单独的做用域,又在匿名函数中建立了一个新的闭包,接收i(num)值,造成了单独的做用域链。spa

  • es6中let方法:
function fun(){
    for(let i=0; i<lis.length; i++){    //此处的length=5
        lis[i].onclick = function(){
            console.log(i);
        }
    }
}

复制代码

此方法的成功,最大的功臣即是let的块级做用域特色,他在每次循环中生成了单独的做用域,达到了与上一种方法相同的效果。翻译

Stage 5

研究这个看似很简单的特性耗费了整整一天的时间,也深深体会到了为何说JS语言的糟粕很多。设计

  • 总结:
    • for循环体内定义函数 ,若函数体内用了for块内的var变量,在for语句外调用该函数时,该函数采用的是循环结束后的var值
    • 而块内用let变量,与之同级的函数体用了该let变量,以后调用函数,函数使用的是定义时块内的let变量值。
  • 收货:更加明确了关于做用域、闭包等概念。尝到了ES6语法的甜头,之后应多使用新标准和新技术。
  • 反思:不应在糟粕的地方太过于钻牛角尖,避免浪费时间。
相关文章
相关标签/搜索