闭包与for循环

一、闭包的形成

有以下代码:

HTML

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

JavaScript

var arr = document.getElementsByTagName("li");
for(var i = 0; i < arr.length; i++){
    arr[i].onclick = function(){
        alert(i);
    }
}

本来期待是点击每个项目弹出来的对应的序号,但是实际上弹出的全都是4,这是由于闭包引起的。

二、闭包形成的原因

在循环体结束之后,你以为是这样的:

arr[0].click=function(){alert(0);};
arr[1].click=function(){alert(1);};
arr[2].click=function(){alert(2);};
arr[3].click=function(){alert(3);};

但是实际上是这样的:

arr[0].click=function(){alert(i);}; //没执行函数,函数内部不变,不能将函数内的i替换!
arr[1].click=function(){alert(i);}; //没执行函数,函数内部不变,不能将函数内的i替换!
arr[2].click=function(){alert(i);}; //没执行函数,函数内部不变,不能将函数内的i替换!
arr[3].click=function(){alert(i);}; //没执行函数,函数内部不变,不能将函数内的i替换!
i=4;

自有被点击的时候才会触发点击事件,所绑定的对应的函数才会去执行,函数执行的时候才会去寻找i的值,这个时候绑定事件的循环早已结束,i已经变成4了,所以无论点击哪一项,找到的i的值都会是4。

那为什么i不会被回收呢?JavaScript具有垃圾回收机制(GC:Garbage Collecation),一般情况下我们使用new创建一个对象实例,使用GC负责回收对象实例占用内存区域。在GC在回收内存之前,会判断对象实例是否被引用,如果确定没有被引用则会释放对象实例所占用的内存。可以使用为对象实例的引用赋值为null,强制销毁对象实例。在这里i被绑定事件的函数所引用,所以不会被回收,会是循环结束后的4。

三、解决方案

1、解决办法一

解决思路:增加若干个对应的闭包域空间(这里采用的是匿名函数),专门用来存储原先需要引用的内容(下标),不过只限于基本类型(基本类型值传递,对象类型引用传递)

for(var i = 0;i<arr.length;i++){

    //声明一个匿名函数,若传进来的是基本类型则为值传递,故不会对实参产生影响,
    //该函数对象有一个本地私有变量arg(形参) ,该函数的 function scope 的 closure 对象属性有两个引用,一个是 arr,一个是 i
    //尽管引用 i 的值随外部改变 ,但本地私有变量(形参) arg 不会受影响,其值在一开始被调用的时候就决定了.
    (function (arg) {
        arr[i].onclick = function () {  //onclick函数实例的 function scope 的 closure 对象属性有一个引用 arg,
            alert(arg);                 //只要 外部空间的 arg 不变,这里的引用值当然不会改变
        }
    })(i);                              //立刻执行该匿名函数,传递下标 i(实参)
}

2、解决方案二

解决思路:将下标作为对象属性(name:”i”,value:i的值)添加到每个数组项(p对象)中。

for(var i = 0;i<arr.length;i++){
    //为当前数组项即当前 p 对象添加一个名为 i 的属性,值为循环体的 i 变量的值,
    //此时当前 p 对象的 i 属性并不是对循环体的 i 变量的引用,而是一个独立p 对象的属性,属性值在声明的时候就确定了
    //(基本类型的值都是存在栈中的,当有一个基本类型变量声明其等于另一个基本变量时,此时并不是两个基本类型变量都指向一个值,而是各自有各自的值,但值是相等的)
    arr[i].i = i;
    arr[i].onclick = function () {
        alert(this.i);
    }
}

3、解决方案三

解决思路:与解决办法一有点相似但却有点不太相似。

  1. 相似点:同样是增加若干个对应的闭包域空间用来存储下标

  2. 不同点:解决办法一是在新增的匿名闭包空间内完成事件的绑定,而此例是将事件绑定在新增的匿名函数返回的函数上。此时绑定的函数中的 function scope 中的 closure 对象的 引用 arg 是指向将其返回的匿名函数的私有变量 arg

for(var i = 0; i<arr.length;i++){
    arr[i].onclick = (function(arg){
        return function () {
            alert(arg);
        }
    })(i);
}

4、解决方案四

解决思路:与解决办法一相同

for(var i = 0; i<arr.length;i++){
    (function(){
       var temp = i;
        arr[i].onclick = function () {
            alert(temp);
        }
    })();
}

5、解决方案五

解决思路:与解决办法三及四相同

for(var i = 0;i<arr.length;i++){
    arr[i].onclick = (function () {
        var temp = i;
        return function () {
            alert(temp);
        }
    })();
}

6、解决方案六

解决思路:将下标添加为绑定函数的属性

for(var i = 0;i<arr.length;i++){
    (arr[i].onclick = function () {
        alert(arguments.callee.i);      //arguments 参数对象  arguments.callee 参数对象所属函数
    }).i = i;
}

7、解决方案七

解决思路:通过 new 使用 Function 的构造函数 创建 Function 实例实现,由于传入的函数体的内容是字符串,故 Function 得到的是一个字符串拷贝,而没有得到 i 的引用(这里是先获取 i.toString()然后与前后字符串拼接成一个新的字符串,Function 对其进行反向解析成 JS 代码)

for(var i = 0;i<arr.length;i++){
    arr[i].onclick = new Function("alert("+i+");");//每 new 一个 Function 得到一个 Function 对象(一个函数),有自己的闭包域
}

8、解决方案八

解决思路:直接通过 Function 返回一个函数
与解决办法七的不同之处在于:

  1. 解决办法七使用 new,使用了 new,此时 Function 函数就被当成构造器可以用来构造一个 Function 实例返回

  2. 当前解决办法没有使用 new ,即将 Function 函数当成一个函数,传入参数返回一个新函数。其实此处 new 与不 new 只是的区别在于:

    1.使用了 new 即 Function 函数充当构造器,由 JS 解析器生产一个新的对象,构造器内的 this 指向该新对象;

    2.不实用 new 即 Function 函数依旧是函数,由函数内部自己生产一个实例返回。

for(var i = 0;i<arr.length;i++){
    arr[i].onclick = Function("alert("+i+");");
}

9、解决方案九

使用ES6新语法 let 关键字 由于几新东西 各浏览器支持不同
chrome 及 opera支持以下语法

<script type="application/javascript">
    "use strict";//使用严格模式,否则报错 SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode
    var arr = document.getElementsByTagName("p");
    for(var i = 0;i<arr.length;i++){
        let j = i;//创建一个块级变量
        arr[i].onclick = function () {
            alert(j);
        }
    }
</script>
firefox支持以下下语法
<script type="application/javascript;version=1.7">
    var arr = document.getElementsByTagName("p");
    for(var i = 0;i<arr.length;i++){
        let j = i;
        arr[i].onclick = function () {
            alert(j);
        }
    }
</script>

由于新语法各大厂商的支持尚未规范故暂不不推荐使用。

参考文章:
https://segmentfault.com/a/1190000003818163
http://www.cnblogs.com/qieguo/p/5457040.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值