Backbone 点击事件回调中的奇怪索引问题

Weird index issue in Backbone click event callback

我有这段代码,它是独立和孤立的。我遇到的问题是索引我从 1 开始而不是从 0 开始。我不知道为什么会这样,而且似乎与我推入删除数组的闭包没有任何关系...但我不能确定,不知道问题出在哪里。

    onClickResetAll: function (event) {
                    event.preventDefault();

                    var deletes = [];

                    Object.keys(collections).forEach(function (key) {
                        if (collections.hasOwnProperty(key)) {

                            var coll = collections[key];

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

                                deletes.push(function (callback) {

                                    var index = i; //i starts at 1, not 0 !!!
                                    coll.models[index].deleteModel(function (err, resp, x) {

                                        console.log(err, resp, x);

                                        if(err){
                                            callback(err);
                                        }
                                        else{
                                            callback(null,null);
                                        }

                                    });
                                });
                            }

                        }
                    });

                    async.parallel(deletes,function(err,results){

                        Backbone.Events.trigger('bootRouter',  '+refreshCurrentPage');

                    });

                }, //end of onClickResetAll callback function

//end

问题不在于 i 从 1 开始,问题在于 i 对于 deletes 中的每个函数都将是 coll.models.length。为什么会这样?好吧,每个函数都共享相同的 i 并且 i 不会被评估,直到实际调用 deletes 中的函数。

解决方案是在 i 具有您想要的值时强制对其求值(即在构建回调函数时对 i 求值)。有多种解决方案,它们都是 "wrap it in a function to break the reference" 主题的变体:

  1. 使用带有回调函数的迭代器而不是普通的 for 循环:

    coll.each(function(model, i) {
        // `model` is the model from the collection, `i` is the loop index.
    });
    

    你可以在这里使用each因为Backbonecollections have a bunch of Underscore functions built in.

  2. 将循环体包装在 SIF 中:

    for(var i = 0; i < coll.models.length; ++i)
        (function(i) {
            //...
        })(i);
    
  3. 使用单独的函数构建函数:

    function make_deleter(coll, i) {
        return function(callback) {
            coll.models[i].deletedModel(function(err, resp, x) {
                //...
            }
         }
    }
    
    //...
    
    for(var i = 0; i < coll.models.length; ++i)
        deletes.push(make_deleter(coll, i));
    

它们几乎都做同样的事情:在组合中添加一个额外的函数调用以强制 i 在循环的每次迭代中被评估(而不是仅仅被引用)。

在 Backbone 的情况下,1 可能是最自然的,你甚至不需要用那种方法麻烦 i

另一个解决方案是使用 async.each 或 async.eachSeries 而不是 async.parallel。使用前两个可以避免完全推送到函数数组。