JavaScript 中怎样完成函数行列?(一)

假定你有几个函数fn1fn2fn3须要按递次挪用,最简朴的体式格局当然是:

fn1();
fn2();
fn3();

但有时刻这些函数是运行时一个个增加进来的,挪用的时刻并不晓得都有些什么函数;这个时刻能够预先定义一个数组,增加函数的时刻把函数push 进去,须要的时刻从数组中按递次一个个掏出来,顺次挪用:

var stack = [];
// 实行其他操纵,定义fn1
stack.push(fn1);
// 实行其他操纵,定义fn2、fn3
stack.push(fn2, fn3);
// 挪用的时刻
stack.forEach(function(fn) { fn() });

如许函数有没名字也不重要,直接把匿名函数传进去也能够。来测试一下:

var stack = [];

function fn1() {
    console.log('第一个挪用');
}
stack.push(fn1);

function fn2() {
    console.log('第二个挪用');
}
stack.push(fn2, function() { console.log('第三个挪用') });

stack.forEach(function(fn) { fn() }); // 按递次输出'第一个挪用'、'第二个挪用'、'第三个挪用'

这个完成目前为止事情一般,但我们疏忽了一个状况,就是异步函数的挪用。异步是JavaScript 中没法防止的一个话题,这里不盘算讨论JavaScript 中有关异步的种种术语和观点,请读者自行查阅(比方某篇有名的评注)。如果你晓得下面代码会输出1、3、2,那请继承往下看:

console.log(1);

setTimeout(function() {
    console.log(2);
}, 0);

console.log(3);

如果stack 行列中有某个函数是相似的异步函数,我们的完成就乱套了:

var stack = [];

function fn1() { console.log('第一个挪用') };
stack.push(fn1);

function fn2() {
    setTimeout(function fn2Timeout() {
         console.log('第二个挪用');
    }, 0);
}
stack.push(fn2, function() { console.log('第三个挪用') });

stack.forEach(function(fn) { fn() }); // 输出'第一个挪用'、'第三个挪用'、'第二个挪用'

题目很明显,fn2确切按递次挪用了,但setTimeout里的function fn2Timeout() { console.log('第二个挪用') }却不是立时实行的(纵然把timeout 设为0);fn2挪用以后立时返回,接着实行fn3fn3实行完了然才真正轮到fn2Timeout
怎样处理?我们剖析下,这里的关键在于fn2Timeout,我们必需比及它真正实行完才挪用fn3,抱负状况下也许像如许:

function fn2() {
    setTimeout(function() {
        fn2Timeout();
        fn3();
    }, 0);
}

但如许做相当于把本来的fn2Timeout全部拿掉换成一个新函数,再把本来的fn2Timeoutfn3插进去。这类动态改掉原函数的写法有个特地的名词叫Monkey Patch。按我们顺序员的口头禅:“做肯定是能做”,但写起来有点拧巴,而且轻易把本身绕进去。有没更好的做法?
我们退一步,不强求等fn2Timeout完整实行完才去实行fn3,而是在fn2Timeout函数体的末了一行去挪用:

function fn2() {
    setTimeout(function fn2Timeout() {
        console.log('第二个挪用');
        fn3();       // 注{1}
    }, 0);
}

如许看起来好了点,不过定义fn2的时刻都还没有fn3,这fn3哪来的?

另有一个题目,fn2里既然要挪用fn3,那我们就不能经由过程stack.forEach去挪用fn3了,不然fn3会反复挪用两次。

我们不能把fn3写死在fn2里。相反,我们只须要在fn2Timeout末端里找出stackfn2的下一个函数,再挪用:

function fn2() {
    setTimeout(function fn2Timeout() {
        console.log('第二个挪用');
        next();
    }, 0);
}

这个next函数担任找出stack 中的下一个函数并实行。我们如今来完成next

var index = 0;

function next() {
    var fn = stack[index];
    index = index + 1; // 实在也能够用shift 把fn 拿出来
    if (typeof fn === 'function') fn();
}

next经由过程stack[index]去猎取stack中的函数,每挪用next一次index会加1,从而到达掏出下一个函数的目标。

next如许运用:

var stack = [];

// 定义index 和next

function fn1() {
    console.log('第一个挪用');
    next();  // stack 中每个函数都必需挪用`next`
};
stack.push(fn1);

function fn2() {
    setTimeout(function fn2Timeout() {
         console.log('第二个挪用');
         next();  // 挪用`next`
    }, 0);
}
stack.push(fn2, function() {
    console.log('第三个挪用');
    next(); // 末了一个能够不挪用,挪用也没用。
});

next(); // 挪用next,终究按递次输出'第一个挪用'、'第二个挪用'、'第三个挪用'。

如今stack.forEach一行已删掉了,我们自行挪用一次nextnext会找出stack中的第一个函数fn1实行,fn1 里挪用next,去找出下一个函数fn2并实行,fn2里再挪用next,依此类推。

每个函数里都必需挪用next,如果某个函数里不写,实行完该函数后顺序就会直接完毕,没有任何机制继承。

了解了函数行列的这个完成后,你应当能够处理下面这道口试题了:

// 完成一个LazyMan,能够根据以下体式格局挪用:
LazyMan(“Hank”)
/* 输出: 
Hi! This is Hank!
*/

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
/* 输出: 
Hi! This is Hank!
// 守候10秒..
Wake up after 10
Eat dinner~
*/

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)
/* 输出: 
Hi This is Hank!
Eat dinner~
Eat supper~
*/

LazyMan(“Hank”).sleepFirst(5).eat(“supper”)
/* 守候5秒,输出
Wake up after 5
Hi This is Hank!
Eat supper
*/

// 以此类推。

Node.js 中赫赫有名的connect框架恰是如许完成中心件行列的。有兴致能够去看看它的源码或许这篇解读《作甚 connect 中心件》

仔细的你能够看出来,这个next临时只能放在函数的末端,如果放在中心,本来的题目还会涌现:

function fn() {
    console.log(1);
    next();
    console.log(2); // next()如果挪用了异步函数,console.log(2)就会先实行
}

reduxkoa 经由过程差别的完成,能够让next放在函数中心,实行完背面的函数再折回来实行next下面的代码,异常奇妙。有空再写写。

    原文作者:lijsh
    原文地址: https://segmentfault.com/a/1190000008320677
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞