与lambda函数参数混淆

时间:2015-08-09 03:22:09

标签: javascript lambda settimeout

我正在尝试在for循环中使用带有lambda函数的setTimeout,但它只从for循环的最后一次迭代中捕获lambda函数内容的最后一个参数。在C#中,我们可以创建一个新变量,以便在每次传递给新的lambda函数时作为参数传递,但这似乎不适用于javascript。有线索吗?

我所说的具体功能是setElementsByIdTimed()

var gElems = new Array();

document.addEventListener("DOMContentLoaded", function (event) {

    //setElementsById('icon_anim_start' , "icon_anim_end");
    //setTimeout(function() {setElementsById('icon_anim_end' , "icon_anim");} , 500);

    var delay = setElementsByIdTimed('icon_anim_start' , "icon_anim_end" , 250);
    setTimeout(function() {setElementsById('icon_anim_end' , "icon_anim");} , delay);
    });

    function getElementsById (elementID){
        var elementCollection = new Array();
        var allElements = document.getElementsByTagName("*");
        for(i = 0; i < allElements.length; i++){
            if(allElements[i].id == elementID)
                elementCollection.push(allElements[i]);

        }
        return elementCollection;
    }

    function setElementsById (elementID, newID) {
        var elems = new Array();
        elems = getElementsById(elementID);

        for (var i = 0; i < elems.length; i++)
        {
            elems[i].id = newID;
        }
    }


    function setElementsByIdTimed (elementID, newID , ms) {
        var elems = new Array();
        elems = getElementsById(elementID);
        console.log("elems.length: " + elems.length);
        gElems = elems;


        for (var i = 0; i < elems.length; i++) {
            var index = i
            setTimeout(function() {
                setElementId(index, newID);
            }, ms * i);
        }

        return ms * (elems.length-1);
    }

    function setElementId (index , newID) {
        console.log ("gElems.length: " + gElems.length + "  index: " + index);
        gElems[index].id = newID;
    }
})

3 个答案:

答案 0 :(得分:1)

这是一个典型的JavaScript闭包问题。基本上,只有一个索引变量实例,并且它在lambda函数的上下文之外声明。所以每个lambda函数都使用相同的索引,并且它们在循环完成后执行,因此index在每次调用时看起来都是越界的。

要使其正常工作index必须具有闭包范围:

function setElementsByIdTimed(elementID, newID , ms)
{
    var elems = new Array();
    elems = getElementsById(elementID);
    console.log("elems.length: " + elems.length);
    gElems = elems;


    for(var i = 0; i < elems.length; i++)
    {
        var index = i
        setTimeout( setClosure(index,newID), ms * i);
    }

    return ms * (elems.length-1);
}
function setClosure( index, newID ) {
    // this lambda is called after the timeout elapses
    return function() {
            setElementId(index, newID);}
}

你也可以玩一个自我调用技巧,但它有点令人头疼:

function setElementsByIdTimed(elementID, newID , ms)
{
    var elems = new Array();
    elems = getElementsById(elementID);
    console.log("elems.length: " + elems.length);
    gElems = elems;


    for(var i = 0; i < elems.length; i++)
    {
        var index = i
        setTimeout( (function(idx,nid) {
                return function () {
                    setElementId(idx,nid);}
            })(index, newID),
            ms * i);
    }

    return ms * (elems.length-1);
}

这些解决方案实际上是相同的,但第一种语法可能更容易理解。

答案 1 :(得分:0)

而不是

    for(var i = 0; i < elems.length; i++)
    {
        var index = i
        setTimeout(function() {
            setElementId(index, newID);
        }, ms * i);
    }

使用IIFE - https://developer.mozilla.org/en-US/docs/Glossary/IIFE

    for(var i = 0; i < elems.length; i++)
    {
        (function(index) {
            setTimeout(function() {
                setElementId(index, newID);
            }, ms * index);
        }(i));
    }

答案 2 :(得分:0)

问题在于

    for(var i = 0; i < elems.length; i++)
    {
        var index = i
        setTimeout(function() {
            setElementId(index, newID);}, ms * i);
    }

索引总是相同的变量。尝试:

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

        (function(index){
          setTimeout(function() {
            setElementId(index, newID);}, ms * i);
        })(i);
    }

每次必须在循环中访问clousure中的变量时,必须使用函数来访问它。您还可以使用forEach应用于数组:

    elems.forEach(function(index){
        setTimeout(function() {
            setElementId(index, newID);}, ms * i);
        }
    });