这篇文章的起源是这样一段代码

http://www.kuaidadi.com/assets/js/animate.js:84

这段代码的作用很简单,就是让一系列的动画,按顺序执行。而这样一层一层嵌套进去的作用就是一个执行完了,执行下一个。

为了解决这个问题我就想到了是不是可以把这些事件放到一个数组里,然后依次执行呢?也就是说实现一个seq(tasks,finally)函数

/**
 * @param {function []} tasks - function array
 * @param {function} cb - 执行完的回调
 */
 function seq(tasks, cb) {
 }

我的思路是每次执行完一个function之后再执行下一个函数,如果执行到最后一个函数就执行最后的cb

function seq(tasks, cb) {
    (function runTask(i) {
        var task = tasks[i];
        task(function() {
            i++;
            if (i !== tasks.length) {
                runTask(i);
            } else {
                cb();
            }
        });
    })(0);
}

但是显然这么实现并不优雅(Zhuāng Bī),那么有没有更优雅的方案呢?显然是有的。我们可以使用ES5reduceRight函数(MSDN文档)
如果你并不知道这个函数的话,我们就要从和它差不多的reduce函数(MSDN文档)说起了。
reduce函数接收两个参数callbackinitialValue

  • callback
    执行数组中每个值的函数,包含四个参数

    • previousValue
      上一次调用回调返回的值,或者是提供的初始值(initialValue)

    • currentValue
      数组中当前被处理的元素

    • index
      当前元素在数组中的索引

    • array
      调用 reduce 的数组

  • initialValue
    作为第一次调用 callback 的第一个参数。

/**
@examples
**/
var array = [
    {
        id: 1,
        name: "小李"
    }, {
        id: 2,
        name: "小张"
    }, {
        id: 3,
        name: "小刘"
    }, {
        id: 4,
        name: "小王"
    }
]
var objMap = array.reduce(function(map,nowObj,index,array) {
    map[nowObj.id] = nowObj;
    return map;
},{});

很简单的就把一个array,转成了一个以idkey,objvalueObjMap有木有?就是这么简单。
reduceRight函数的参数和reduce函数一样,只不过是执行的顺序与reduce函数相反,是倒过来执行的。这样的话我们就可以把上面那个tasks转换成类似最一开始那张图一样的结构。

function seq2(tasks, cb) {
    tasks.reduceRight(function(cb, task) {
        return function() {
            return task(cb);
        };
    }, cb)();
}
//这段代码的推导如下
//第一次执行
function() {
    return task5(cb);
};
//第二次执行
function() {
    return task4(function() {
        return task5(cb);
    });
}
//第三次执行
function() {
    return task3(function() {
        return task4(function() {
            return task5(cb);
        });
    });
};
//第四次执行
function() {
    return task2(function() {
        return task3(function() {
            return task4(function() {
                return task5(cb);
            });
        });
    })
};
//最后一次执行
function() {
    return task1(function() {
        return task2(function() {
            return task3(function() {
                return task4(function() {
                    return task5(cb);
                });
            });
        });
    });
};

这样就得到了一个跟上面最一开始那张图一模一样的一个function,然后最重要的是reduceRight面那个(),他就是启动这个最终返回function的开关。
测试用例如下:

var tasks = [1, 2, 3, 4, 5];
tasks = tasks.map(function(i) {
    return function(cb) {
        setTimeout(function() {
            console.log(i);
            cb();
        }, Math.random() * 500 | 0);
    }
});
seq(tasks, function() {
    console.log('all Done');
});
seq2(tasks, function() {
    console.log('all Done');
});

这个问题是我公司的一位同事(老师)给我出的题目。第二个方案,也是他给我的思路。感谢我的这位老师,虽然在工作中我说的不多,但是在我内心里,我对您的崇拜那是简直了。哈哈。最后是github源代码


hiyangguo
821 声望75 粉丝

宏路数据F2E