实现Lazyman

前言

之前在掘金上到一篇文章关于微信面试的文章,其中提到了手动实现Lazyman的问题。刚开始
看到Lazyman我是一脸懵逼的,这是什么鬼,后来查了查了一下,才发现,其实就是手动实现
以下功能:

实现一个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
     
以此类推。

看起来好像挺有趣的,我们来分析以下

分析

首先我们看到例子有几个特点,一个是我们调用Lazyman的时候不需要用到new关键字,这意味着我们需要使用工厂函数;另一个是要我们实现链式调用。
我们看到LazyMan(“Hank”).eat(“dinner”).eat(“supper”)这样的形式,无疑是链式调用了;还有一个难点就是 LazyMan(“Hank”).sleepFirst(5).eat(“supper”)
当存在sleepFirst时,我们还要先等待一段时间,然后再开始报名字,这就说明sleepFirst优先级更高,不管何时注册,都要第一个执行,仔细想想
有什么可以实现这个呢?明显我们需要一个任务队列,而且sleepFirst放在最前面,然后等所有任务都安排好了,才开始执行任务队列
恩?那说明执行任务不能紧跟在插入任务全程的后面,那我们见他们分进两个事件队列就好了,这就需要借助setTimeout函数了;
除此之外,一个任务完成了,我们怎么通知任务队列去取下一个任务呢?这就需要一个尾调用。

编码

经过上面的分析,我们可以开始编码了:
首先,我们先写工厂函数

function Lazyman ( name ) {
    return new _Lazyman ( name );
}

接着我们开始实现Lazyman:

constructor ( name ) {
    this.tasks = [];//设置任务队列
    let task = (name => () => {
    console.log ( `Hi! This is ${name} !` );
    this.next ();
        }) ( name );
    this.tasks.push ( task );
    //通过settimeout的方法,将执行函数放入下一个事件队列中,从而达到先注册事件,后执行的目的
    setTimeout ( () => {
      this.next ();
    }, 0 );
}
//尾调用函数,一个任务执行完然后再调用下一个任务
next () {
  let task = this.tasks.shift ();
  task && task ();
}
eat ( food ) {
    let task = (food => () => {
        console.log ( `Eat ${food}` );
        this.next ();
    }) ( food );
    this.tasks.push ( task );
    return this;
}

sleep ( time ) {
    let task = (time => () => {
        setTimeout ( () => {
            console.log ( `Wake up after ${time} s!` );
            this.next ();
        }, time * 1000 )
    }) ( time );
    this.tasks.push ( task );
    return this;
}
sleepFirst ( time ) {
    let task = (time => () => {
        setTimeout ( () => {
            console.log ( `Wake up after ${time} s!` );
            this.next ();
        }, time * 1000 )
    }) ( time );
    this.tasks.unshift ( task );//sleepFirst函数需要最先执行,所以我们需要在任务队列前面放入,然后再执行后面的任务
    return this;
}

通过上面的步骤,我们就实现了一个简单的Lazyman了

改进

上面明明实现了一个Lazyman了呀,还有什么可以改进的?当然有,如果我们调用eat的时候,想输出的是Eaaaaaaaaaat ${food}!!!!,然后输出好饱啊好饱啊
这意味着每次改变,我们都要去修改eat这个函数,这就耦合度太高了,这时候,我们可以采用发布订阅的方式,在调用eat的时候,我们注册一个监听函数,然后当任务
执行的时候再发布这个事件,让对应的监听函数执行,这样就实现了解耦了

总结

一个小小的Lazyman,竟然有如此多的考点,是在让人受益匪浅,当然,Lazyman还可以使用promise的方式实现,当然实现一个手写的Promise实在有点难(逃),有机会再用promise
实现一次哈