JavaScript之经典闭包问题

闭包

返回函数不要引用任何循环变量,或者后续会发生变化的变量。
=. =
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值(函数作为返回值),形成闭包,无论该循环变量后续如何更改,已绑定到函数参数的值不变。

DEMO1

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都添加到一个Array中返回了。

你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:

f1(); // 16
f2(); // 16
f3(); // 16

全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。

返回闭包时牢记的一点就是返回函数不要引用任何循环变量,或者后续会发生变化的变量

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9

DEMO2

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <!-- 在页面添加三个按钮 -->
    <input type="button" value="1">
    <input type="button" value="2">
    <input type="button" value="3">


    <script>
        //by zhengkai.blog.csdn.net
        // 获取页面所有的input
        var allBtn = document.getElementsByTagName('input');

        //请自己取消三段注释,感受一下效果

        //循环绑定点击事件,当然毫无疑问这里点击之后会弹出3,
        //因为我们知道js是单线程的,当基本逻辑语句执行完之后,才会执行点击事件
        //而当你触发点击事件的时候,for循环都已经跑完了,所以i已经变成了3;
        for(var i = 0; i < allBtn.length; i++){
            console.log("allBtn["+i+"]");
            allBtn[i].onclick = function(){
                console.log("alert("+i+")");
            }
        }

        // 解决方法1:也是最简单的方法,就是将for循环的var变成let
        //这样当点击每个按钮的时候,就会依次弹出0,1,2;
        //let是ES6新增的一个变量声明方式,拥有块级作用域;
        // for(let i = 0; i < allBtn.length; i++){
        //     console.log("allBtn["+i+"]");
        //     allBtn[i].onclick = function(){
        //         console.log("alert("+i+")");
        //     }
        // }

        //解决方法2:利用闭包(说是闭包,貌似也不完全是),也就是函数作用域访问原则的特性
        //函数内部可以访问外部的变量,外部却访问不了里边的;
        // for(var i = 0; i < allBtn.length; i++){
        //     console.log("allBtn["+i+"]");
        //     (function(i){
        //         allBtn[i].onclick = function(){ 
        //             console.log("alert("+i+")");
        //         }
        //     })(i);
        // }
    </script>
</body>
</html>

ConsoleOutput

经典问题,肯定是有问题的啦,控制台输出了alert(3).

因为我们知道js是单线程的,当基本逻辑语句执行完之后,才会执行点击事件,而当你触发点击事件的时候,for循环都已经跑完了,所以i已经变成了3

allBtn[0] 
allBtn[1] 
allBtn[2] 
alert(3)
alert(3)
alert(3)

优化后的let方法或者闭包方法输出如下

allBtn[0] 
allBtn[1] 
allBtn[2] 
alert(0)
alert(1)
alert(2)
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页