一个面试题,有啥想法么?

实现函数LazyMan,使得:

LazyMan('Hank'); 输出
Hi Hank!

LazyMan('Hank').eat('dinner');输出
Hi Hank!
Eat dinner!

LazyMan('Hank').sleep(5).eat('dinner'); 输出:
Hi Hank!
//等待五秒
Eat dinner!

LazyMan('Hank').sleepFirst(5).eat('dinner');输出:
//等待五秒
Hi Hank!
Eat dinner!
阅读 6.5k
10 个回答

2017-4-12 更新,美化下输出结果。

function LazyMan(nick){
    var obj = {
        task : {
            list : [],
            add : function(fun, timer){
                timer = timer || 0;
                this.task.list.push({
                    fun : fun,
                    timer : timer
                });
                return this;
            },
            run : function(){
                if( this.task.list.length > 0 ){
                    setTimeout( (function(){
                        this.task.list.shift().fun.call(this);
                    }).bind(this), this.task.list[0].timer );
                }else{
                    this.echo("[Task]", "==========Stop==========");
                }
            }
        },
        echo : function( str , str2 ){
            var message = {
                isLog : !!str.match(/^\[[\s\S]*\]$/),
                style : [
                    'color:#090;font-size:1.2em;font-weight:800;',
                    'color:#CCC;font-size:0.5em;font-style:italic;',
                ],
                text : str + str2
            };
            if( 'table' in console ){
                //
                console.log('%c%s', message.style[+message.isLog], message.text );
            }else{
                !message.isLog && console.log(message.text );
            }
            delete message;
            return this;
        },
        hello : function( nick ){
            return this.task.add.call(this, function(){
                this.echo('Hi', nick);
                this.task.run.call(this);
            });
        },
        eat : function( eat ){
            return this.task.add.call(this, function(){
                this.echo('Eat', eat);
                this.task.run.call(this);
            });
        },
        sleep : function( timer ){
            return this.task.add.call(this, function(){
                this.echo("[Timer( sleep )]", timer);
                this.task.run.call(this);
            }, timer * 1000);
        },
        sleepFirst : function( timer ){
            var fun = this.task.list[0].fun;
            this.task.list[0].fun = function(){
                setTimeout((function(){
                    this.echo("[Timer( sleepFirst) ]", timer);
                    fun.call(this);
                }).bind(this), timer * 1000);
            };
            return this;
        }
    };
    obj.echo("[Task]", "==========Start==========").hello(nick).task.run.call(obj);
    return obj;
};

LazyMan("A").sleepFirst(1).eat("abc").sleep(4).sleep(5).eat("A").eat("B").eat("C")

======================

2017-3-22 代码再次更新,受一道面试题的启发。重新优化了代码(不等待时,setTimeout的间隔时间为0):

function LazyMan(nick){
    var obj = {
        task : {
            list : [],
            add : function(fun, timer){
                timer = timer || 0;
                this.task.list.push({
                    fun : fun,
                    timer : timer
                });
                return this;
            },
            run : function(){
                if( this.task.list.length > 0 ){
                    setTimeout( (function(){
                        this.task.list.shift().fun.call(this);
                    }).bind(this), this.task.list[0].timer );
                }else{
                    this.echo("[Task]", "==========Stop==========");
                }
            }
        },
        echo : function( str , str2 ){
            console.log( str + ' ' + str2 );
            return this;
        },
        hello : function( nick ){
            return this.task.add.call(this, function(){
                this.echo('Hi', nick);
                this.task.run.call(this);
            });
        },
        eat : function( eat ){
            return this.task.add.call(this, function(){
                this.echo('Eat', eat);
                this.task.run.call(this);
            });
        },
        sleep : function( timer ){
            return this.task.add.call(this, function(){
                this.echo("[Timer( sleep )]", timer);
                this.task.run.call(this);
            }, timer * 1000);
        },
        sleepFirst : function( timer ){
            var fun = this.task.list[0].fun;
            this.task.list[0].fun = function(){
                setTimeout((function(){
                    this.echo("[Timer( sleepFirst) ]", timer);
                    fun.call(this);
                }).bind(this), timer * 1000);
            };
            return this;
        }
    };
    obj.echo("[Task]", "==========Start==========").hello(nick).task.run.call(obj);
    return obj;
};

LazyMan("A").sleepFirst(1).eat("abc").sleep(4).sleep(5).eat("A").eat("B").eat("C")

得到启发的代码如下:

var a = true;
setTimeout(function(){
    a = false;
}, 0);
while( a == true ){}

alert('x'); //alert永远不会执行。JS作为一个单线程语言,只有处理完队列中的任务后,才会去去调用setTimeout的队列。

以下是原答案:

重新来答一遍,这次是认真的,使用了任务队列,同样只使用了一个函数,没用使用Promise,没有使用 timer累加的方法,保证最多只有一个计时器在运行,同时能获取任务开始和最终结束时间。

核心部分是一个 setTimeout 和 一个 任务队列(数组)。

因为使用了队列流程,处理完一个再处理下一个,所以支持多次输出,多次等待,而程序不会乱掉。 LazyMan("A").sleepFirst(1).eat("abc").sleep(4).sleep(5).eat("A").eat("B").eat("C")

function LazyMan(nick){
    var obj = {
        task : {
            list : [],
            add : function(timer, fun){
                timer = timer || 16;
                this.task.list.push({
                    fun : fun,
                    timer : timer
                });
                return this;
            },
            run : function(){
                if( this.task.list.length > 0 ){
                    setTimeout( (function(){
                        this.task.list.shift().fun.call(this);
                    }).bind(this), this.task.list[0].timer );
                }else{
                    this.echo("[Task]", "==========Stop==========");
                }
            }
        },
        echo : function( str , str2 ){
            console.log( str + ' ' + str2 );
            return this;
        },
        hello : function( nick ){
            return this.task.add.call(this, 16, function(){
                this.echo('Hi', nick);
                this.task.run.call(this);
            });
        },
        eat : function( eat ){
            return this.task.add.call(this, 16, function(){
                this.echo('Eat', eat);
                this.task.run.call(this);
            });
        },
        sleep : function( timer ){
            return this.task.add.call(this, timer * 1000, function(){
                this.echo("[Timer( sleep )]", timer);
                this.task.run.call(this);
            });
        },
        sleepFirst : function( timer ){
            var fun = this.task.list[0].fun;
            this.task.list[0].fun = function(){
                setTimeout((function(){
                    this.echo("[Timer( sleepFirst) ]", timer);
                    fun.call(this);
                }).bind(this), timer * 1000);
            };
            return this;
        }
    };
    obj.echo("[Task]", "==========Start==========").hello(nick).task.run.call(obj);
    return obj;
};

测试下结果:

LazyMan('Hank');
/*
[Task] ==========Start==========
Hi Hank
[Task] ==========Stop==========
*/

LazyMan('Hank').eat('dinner');
/*
[Task] ==========Start==========
Hi Hank
Eat dinner
[Task] ==========Stop==========
*/

LazyMan('Hank').sleep(5).eat('dinner');
/*
[Task] ==========Start==========
Hi Hank
[Timer( sleep )] 5
Eat dinner
[Task] ==========Stop==========
*/

LazyMan('Hank').sleepFirst(5).eat('dinner');
/*
[Task] ==========Start==========
[Timer( sleepFirst) ] 5
Hi Hank
Eat dinner
[Task] ==========Stop==========
*/

LazyMan('来搞笑的小吃货').sleepFirst(1).eat('西瓜').eat('甜瓜').eat('哈密瓜').sleep(2).eat('苹果').eat('橘子').eat('香蕉').sleep(3).sleep(4).eat('撑死了');
/*
[Task] ==========Start==========
[Timer( sleepFirst) ] 1
Hi 来搞笑的小吃货
Eat 西瓜
Eat 甜瓜
Eat 哈密瓜
[Timer( sleep )] 2
Eat 苹果
Eat 橘子
Eat 香蕉
[Timer( sleep )] 3
[Timer( sleep )] 4
Eat 撑死了
[Task] ==========Stop==========
*/

正规作答结束,以下是原答案:


民科解法,欢迎来喷。强迫症,没有使用 任务队列,和 @xiaoboost 答案不同的是,不支持 LazyMan('Hank').sleepFirst(2).sleep(3).eat('lunch').sleep(4).eat('dinner'); 这种方式调用。

不在乎中间过程,只在乎结果。

稍后用队列的方法,重写一遍。

function LazyMan(nick){
    var obj = {
        data : {},
        echo : function( str , str2 ){
            console.log( str + ' ' + str2 );
        },
        hello : function( nick ){
            this.data.nick = nick || this.data.nick;
            setTimeout( (function(){
                if( ! this.data.waiting ){
                    this.echo('Hi', this.data.nick);
                }
            }).bind(this), 16);
            return this;
        },
        eat : function( eat ){
            this.data.eat = eat || this.data.eat;
            setTimeout( (function(){
                if( ! this.data.sleep ){
                    this.echo('Eat', this.data.eat);
                }
            }).bind(this), 16);
            return this;
        },
        sleep : function( timer ){
            this.data.sleep = true;
            setTimeout( (function(){
                this.data.sleep = false;
                this.eat();
            }).bind(this), timer * 1000);
            return this;
        },
        sleepFirst : function( timer ){
            this.data.waiting = true;
            this.data.sleep = true;
            setTimeout( (function(){
                this.data.waiting = false;
                this.hello();
                this.data.sleep = false;
                this.eat();
            }).bind(this), timer * 1000);
            return this;
        }
    };
    return obj.hello(nick);
}

这道题有点意思,要完成这个任务有点麻烦,我算是稍微取了点巧吧。

我个人是使用了异步来实现的。
Man类内部维护了一个任务队列,所有的类方法都会直接操作任务队列的顺序。
由于类生成时候设定的异步函数必须要等主进程完成之后才会启动,所以它是不会影响到主进程时候的任务队列操作的。
主进程运行结束,类定义时候的setTimeout进程启动,它会把队列中的所有方法用then串联起来,之后任务队列就会按照顺序依次运行了。

当然这还是能随意组合的,比如这样:

LazyMan('Hank').sleepFirst(2).sleep(3).eat('lunch').sleep(4).eat('dinner');
// 等待2秒
// Hi Hank
// 等待3秒
// eat lunch
// 等待4秒
// eat dinner

代码在这里:

function Man(str) {
    // 默认函数
    function start(resolve) {
        console.log(`Hi ${str}`);
        resolve();
    }
    // 初始化队列
    this.queue = [start];
    
    setTimeout(() => {
        let start = Promise.resolve();
        for (let i = 0; i < this.queue.length; i++) {
            start = start.then(() => new Promise(this.queue[i]));
        }
    })
}
Man.prototype.eat = function(food) {
    function eatFood(resolve) {
        console.log(`Eat ${food}`);
        resolve();
    }
    this.queue.push(eatFood);
    return this;
};
Man.prototype.sleep = function(time) {
    function sleep(resolve) {
        setTimeout(resolve, time * 1000);
    }
    this.queue.push(sleep);
    return this;
};
Man.prototype.sleepFirst = function(time) {
    function sleep(resolve) {
        setTimeout(resolve, time * 1000);
    }
    this.queue.splice(0, 0, sleep);
    return this;
};

function LazyMan(...args) {
    return new Man(...args);
}

赶项目进度中换换脑子,随手试写。可以添加新方法,可以任意组合。感觉写得一般。

LazyMan('Hank').sleepFirst(5).sleep(5).eat('breakfast').sleep(5).eat('lunch').sleep(5).eat('dinner');
function LazyMan(name) {
  const lazyMan = {
    name,
    timer: 0,
    firstTimer: 0,
    init: false,
    
    handle: function(cb, prop) {
      const _this = this;
      
      if(!this.init) {
        this.init = true;
        if(this.firstTimer > 0) {
          setTimeout(function() {
            _this.say(_this.name);
          }, _this.firstTimer * 1000);
        } else {
          _this.say(_this.name);
        }
      }
      
      setTimeout(function() {
        cb.call(_this, prop);
      }, this.timer * 1000);
    },

    eat: function(food) {
      this.handle(function(food) {
        console.log(`Eat ${food}`);
      }, food);
      return this;
    },

    sleep: function(timer) {
      this.timer += timer;
      return this;
    },

    sleepFirst: function(timer) {
      this.timer += timer;
      this.firstTimer += timer;
      return this;
    },

    say: function(name) {
      console.log(`Hi ${name}`);
      return this;
    }
  }
  return lazyMan;
}

前些天看过这个问题,https://www.madcoder.cn/inter...,我把这个大牛写的答案复制到这里。


class C {
    constructor (name) {
        this.tasks = [];
        setTimeout(() => this.next());
        return this.push(() => new Promise(r => console.log(`Hi! This is ${name}`) || r()));
    }
    next () {
        let task = this.tasks.shift();
        task && task().then(() => this.next());
    }
    push (v) {
        this.tasks.push(v);
        return this;
    }
    unshift (v) {
        this.tasks.unshift(v);
        return this;
    }
    sleep (sec) {
        return this.push(() => new Promise(r => console.log(`//等待${sec}秒..`) || setTimeout(() => console.log(`Wake up after ${sec}`) || r(), 1000 * sec)));
    }
    sleepFirst (sec) {
        return this.unshift(() => new Promise(r => console.log(`//等待${sec}秒..`) || setTimeout(() => console.log(`Wake up after ${sec}`) || r(), 1000 * sec)));
    }
    eat (name) {
        return this.push(() => new Promise(r => console.log(`Eat ${name}`) || r()));
    }
}
const LazyMan = function (name) {
    return new C(name);
}
LazyMan('Gcaufy').eat('dinner').sleep(3).eat('supper').sleepFirst(2);

我也来写一个

var lazymen=(function(){
        var arr=[];
        var i=0;
        function next(){
            console.log(arr)
            if(arr[i]){
                arr[i++]();
            }else{i=0;arr=[]}
            
        }
        var cb=function(fn){
            var arg=[].slice.call(arguments,1)
            return function(){
                fn.apply(null,arg)
            }
        }
        var say=function(x,y){
            console.log(x+' '+y);
            next()
        }
        var lazymen=function(str){
            arr.push(cb(say,'Hi',str))
            setTimeout(function(){
                next()
            },0)
        }
        var  sleep=function(num){
                setTimeout(function(){next()},num*1000)
        }
        lazymen.prototype.eat=function(str){
            arr.push(cb(say,'eat',str))
            return this
        };
        lazymen.prototype.sleep=function(num){
            arr.push(cb(sleep,num))
            return this
        }
        lazymen.prototype.sleepFirst=function(num){
            arr.unshift(cb(sleep,num))
            return this
        }
        return lazymen
    })()
    var LazyMan=function(str){
        return new lazymen(str)
    }
新手上路,请多包涵
const delay = d => new Promise(resolve => setTimeout(resolve, d))

class LM {
  constructor(name) { 
    this.name = name 
    this.queue = Promise.resolve()
        .then(_ => this.first)
        .then(_ => console.log(`Hi ${this.name}`))
  }
  
  eat(food) {
    this.queue = this.queue.then(_ => console.log(`Eat ${food}`))
    return this
  }
  
  sleep(d) {
    this.queue = this.queue.then(_ => delay(d * 1000))
    return this
  }
  
  sleepFirst(d) {
    this.first = delay(d * 1000)
    return this
  }
}

function LazyMan(name) {
  return new LM(name)
}
const LazyMan = function (name) {
  let queue = []
  const cb = function () {
    const fn = queue.shift()
    fn && fn()
  }
  setTimeout(function () {
    cb()
  })
  queue.push(function () {
    console.log(`hi ${name}`)
    cb()
  })
  return {
    sleep: function (time) {
      queue.push(function () {
        setTimeout(cb, time * 1000)
      })
      return this
    },
    sleepFirst: function (time) {
      queue.splice(0, 0, function () {
        setTimeout(cb, time * 1000)
      })
      return this
    },
    eat: function (food) {
      queue.push(function () {
        console.log(`eat ${food}`)
        cb()
      })
      return this
    }
  }
}
LazyMan('alex').sleep(1).eat('?').sleepFirst(2).sleep(2).eat('?')

有点 Promise 的味道,参考了 pinkie 的实现

/**
 * https://segmentfault.com/q/1010000008745355
 * 
 * @author Ivan Yan
 */

function Callbacks() {
  this.queue = []
  this.timer = false
}

Callbacks.prototype.push = function (callback, wait) {
  this.queue.push([callback, wait])

  if (!this.timer) {
    this.timer = true
    var next = typeof setImmediate === 'undefined' ? setTimeout : setImmediate
    next(this.flush.bind(this), 0)
  }
}

Callbacks.prototype.delayAll = function (callback, wait) {
  this.queue.unshift([callback, wait])
}

Callbacks.prototype.flush = function () {
  while (this.queue.length) {
    var item = this.queue.shift()
    var wait = item[1]
    if (wait) {
      setTimeout(this.flush.bind(this), wait)
      return
    }

    item[0]()
  }

  this.queue = []
  this.timer = false
}

function noop() {}

function LazyMan(name) {
  if (!(this instanceof LazyMan)) {
    return new LazyMan(name)
  }
  this.name = name
  this.callbacks = new Callbacks()
  this.hi()
}

LazyMan.prototype._then = function (callback) {
  this.callbacks.push(callback)
  return this
}

LazyMan.prototype.hi = function () {
  var name = this.name
  return this._then(function () {
    console.log('Hi ' + name)
  })
}

LazyMan.prototype.eat = function (sth) {
  return this._then(function () {
    console.log('Eat ' + sth)
  })
}

LazyMan.prototype.sleep = function (s) {
  this.callbacks.push(noop, s * 1000)
  return this
}

// sleepFirst 延迟了前面的回调 hi
// 问题是延迟前面一个,还是延时前面所有的?
// 这里延时所有的回调
LazyMan.prototype.sleepFirst = function (s) {
  this.callbacks.delayAll(noop, s * 1000)
  return this
}

// test
// LazyMan('Hank')
// LazyMan('Hank').eat('dinner')
// LazyMan('Hank').sleep(5).eat('dinner')
// LazyMan('Hank').sleepFirst(5).eat('dinner')
// LazyMan('Hank').sleepFirst(2).eat('dinner').sleepFirst(3) // 同上
LazyMan('Hank').sleepFirst(5).eat('launch').sleep(5).eat('dinner')
新手上路,请多包涵
function Lazy(nick) {
    const start  = () => {
        console.log(`Hello ${this.nick}`)
    }
    this.nick = nick
    this.tasks = []
    this.globalTime = 0
    this.tasks.push({
        run: start
    })
    setTimeout(() => {
        this.tasks.forEach(task => {
            task['time'] && (this.globalTime += task.time)
            setTimeout((() => {
                task['run'] && task.run()
            }).bind(this), this.globalTime)
        })
    })
}

Lazy.prototype.addTask = function(cb, time) {
    var lastTask = this.tasks[this.tasks.length - 1]
    if (!lastTask['run']) {
        lastTask.run = cb
    } else {
        this.tasks.push({
            run: cb,
            time: time === undefined ? time : time * 1000
        })
    }
    return this
}
Lazy.prototype.eat = function(str) {
    this.addTask(((str) => {
        return () => {
            console.log(`eat ${str}`)
        }
    })(str))
    return this
}
// 修改前一个task的时延
Lazy.prototype.sleepFirst = function(time) {
    var task = this.tasks[this.tasks.length-1]
    task.time = time * 1000
    return this
}
// 修改后一个任务的时延
Lazy.prototype.sleep = function(time) {
    this.tasks.push({
        time: this.globalTime + time * 1000
    })
    return this
}

function LazyMan(nick) {
    return new Lazy(nick)
}

今天刚做了这道题,开始有点懵逼,后来才意识到可以基于任务去处理
主要基于以下几点解决问题
1.setTimeout的代码会在执行完其他代码后才执行,并且同时延的基于其先后顺序执行
2.维护任务组遍历执行
3.维护全局时钟用于确定任务的执行时间
缺点:
1.定时器比较多,可以考虑吧更改为setInterval定时轮询
2.子任务使用闭包存储传参,太多了容易内存oom,可以添加到task任务描述上

推荐问题
宣传栏