面试遇到LazyMan,盘它!

738 阅读2分钟

// 实现一个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 需要注意几个点:

  1. 顺序问题,一般会按顺序执行,但可以插入任务(sleepFirst)
  2. 链式调用

解决第一个点,可以选择用队列(数组来模拟)来存起来链式调用的方法,因为数组可以满足:顺序,插入任务这两个条件。但也引出了2个问题:

  1. 队列存起来的方法,出队时才会执行打印对应的语句的操作,可以理解为,出队的时候才会执行方法的真正操作
  2. 执行完后,怎样执行下一个方法呢

为了解决问题1,可以选择返回一个可执行函数来解决,一般来说会有参数传入,可以选择IIFE来处理

var fn = (function(args) {
  var args = args;
  return function() {
    // 可以使用args
    ...
  }
})(args)

解决问题2,我们可以造一个方法,用来顺序地让队列中的方法出队且主动调用队列中的方法。

// 只是一个示意函数,tasks是任务队列
function next() {
  var fn = tasks.pop();
  typeof fn === 'function' && fn();
}

好了,这里已经是解决掉了第一个点,那么第二点,需要链式调用呢,这个比较简单,方法返回this即可。

那现在写一下整体的代码吧。


function LazyMan(name) {
    // 判断 this 是否指向 LazyMan
    if (!(this instanceof LazyMan)) {
        return new LazyMan(name);
    }

    var self = this;
    this.tasks = [];
  	// 关键1: IIFE返回一个可执行函数
    var fn = (function(name){
        return function() {
            console.log('Hi!This is ' + name);
            self.next();
        }
    })(name);
    // 默认状态下,调用LazyMan,会返回 fn
    this.tasks.push(fn);
    setTimeout(function() {
        // 关键2: 开始调用队列的第一个方法
        self.next();
    }, 0)
}
// 关键3: next 方法
LazyMan.prototype.next = function() {
  	// 出队
    var fn = this.tasks.shift();
    typeof fn === 'function' && fn();
};

LazyMan.prototype.eat = function(x) {
    let self = this;
    var fn = (function(x) {
        return function() {
            console.log('Eat '+ x);
            self.next();
        }
    })(x);
    this.tasks.push(fn);
    // 关键4: 链式调用的关键
    return this;
};

LazyMan.prototype.sleep = function(x) {
    let self = this;
    var fn = (function(x) {
        return function() {
            setTimeout(function() {
                console.log('Wake up after '+ x + ' s');
                self.next();
            }, x * 1000);
        }
    })(x);
    this.tasks.push(fn);
    return this;
};

LazyMan.prototype.sleepFirst = function(x) {
    let self = this;
    var fn = (function(x) {
        return function() {
            setTimeout(function() {
                console.log('Wake up after '+ x + ' s');
                self.next();
            }, x * 1000);
        }
    })(x);
  	// 关键5: 插到队列最前面
    this.tasks.unshift(fn);
    return this;
};

LazyMan('alili').sleep(2).eat('dinner').sleepFirst(1);
// Wake up after 1 s
// Hi!This is alili
// Wake up after 2 s
// Eat dinner

参考:

前端工程师面试题(js) - 如何实现一个LazyMan?