zoukankan      html  css  js  c++  java
  • js闭包陷阱问题

    JavaScript是一种非常强大的函数式编程语言,可以动态创建函数对象。

    由于JavaScript还支持闭包(Closure),因此,函数可以引用其作用域外的变量,非常强大。

    来看看在JavaScript中使用闭包的陷阱:

    var tasks = [];
    
    for (var i=0; i<3; i++) {
        tasks.push(function() {
            console.log('>>> ' + i);
        });
    }
    
    console.log('end for.');
    
    for (var j=0; j<tasks.length; j++) {
        tasks[j]();
    } 

    如果在循环中创建函数,并引用循环变量,原意是打印出0,1,2,但结果却是一样的:

    end for.
    >>> 3
    >>> 3
    >>> 3 

    这个问题的原因在于,函数创建时并未执行,所以先打印end for.,然后才执行函数。

    由于函数引用了循环变量i,在函数执行时,由于i的值已经变成了3,所以,打印出的结果不对。

    如果我们用一个变量n来复制一份i传入呢?

    var tasks = [];
    
    for (var i=0; i<3; i++) {
        var n = i;
        tasks.push(function() {
            console.log('>>> ' + n);
        });
    }
    
    console.log('end for.');
    
    for (var j=0; j<tasks.length; j++) {
        tasks[j]();
    } 

    结果还是不对:

    end for.
    >>> 2
    >>> 2
    >>> 2 

    注意到i会比n多加一次,所以n的最终值是2

    这个问题的原因是JavaScript虽然有局部变量,但局部变量只有函数作用域,而没有其他语言通常有的块作用域(循环内部,if内部),所以,无论在函数内部的哪个地方定义变量,变量作用域都是整个函数。

    把上述代码的变量提取到函数开头,其实是这样的:

    var tasks, i, n, j;
    
    tasks = [];
    
    for (i=0; i<3; i++) {
        n = i;
        tasks.push(function() {
            console.log('>>> ' + n);
        });
    }
    
    console.log('end for.');
    
    for (j=0; j<tasks.length; j++) {
        tasks[j]();
    } 

    完全等价。

    根据道爷的说法,JavaScript缺少块作用域是语言设计的一个缺陷。所以,上面的代码有问题,就是因为n的作用域是整个函数,而不是循环。

    创建闭包的一条原则就是:

    不要引用循环变量!

    这条原则对有没有块作用域的函数式编程语言都适用。

    如果一定要在闭包中引用循环变量怎么办???

    方法是再创建一个函数,将循环变量作为函数参数传入:

    var tasks = [];
    
    for (var i=0; i<3; i++) {
        var fn = function(n) {
            tasks.push(function() {
                console.log('>>> ' + n);
            });
        };
        fn(i);
    } 

    这段代码是正确的,是因为循环内部,fn()函数立即被执行,因此,闭包拿到的参数n就是当前循环变量的值的副本。

    最后,简化语法,直接用匿名函数的立即执行模式(function() { ... })()改写如下:

    var tasks = [];
    
    for (var i=0; i<3; i++) {
        (function(n) {
            tasks.push(function() {
                console.log('>>> ' + n);
            });
        })(i);
    }


    来自:http://www.liaoxuefeng.com/article/001398146224424978bbe1df8594901b2d477d1619cd7ca000
  • 相关阅读:
    jQuery EasyUI API 中文文档 数字框(NumberBox)
    jQuery EasyUI API 中文文档 数值微调器(NumberSpinner)
    jQuery EasyUI API 中文文档 日期时间框(DateTimeBox)
    jQuery EasyUI API 中文文档 微调器(Spinner)
    jQuery EasyUI API 中文文档 树表格(TreeGrid)
    jQuery EasyUI API 中文文档 树(Tree)
    jQuery EasyUI API 中文文档 属性表格(PropertyGrid)
    EntityFramework 数据操作
    jQuery EasyUI API 中文文档 对话框(Dialog)
    jQuery EasyUI API 中文文档 组合表格(ComboGrid)
  • 原文地址:https://www.cnblogs.com/lechie/p/3698096.html
Copyright © 2011-2022 走看看