从一道“微信面试题”能学到什么?

关于题目的说明

为什么取得是这样的一个题目,这个其实不难理解,作为BAT中的一员,tx的面试题往往会成为一段时间之内的某些领域的风向标。这些面试题可能并不难,但是涵盖了很多方面的知识,需要我们谨慎应对。

题目内容:实现一个LazyMan,按照下面的方式进行调用,得到相关输出:

LazyMan("mulige")

// Hi! This is mulige!

LazyMan("mulige").sleep(10).eat('dinner')

// Hi! This is mulige!
// 等待10秒...
// Wake up after 10
// Eat dinner

LazyMan("mulige").eat("dinner").eat("supper")

// Hi! This is mulige!
// Eat dinner
// Eat supper

LazyMan("mulige").sleepFirst(10).eat("supper")

// 等待10秒...
// Wake up after 10
// Hi! This is mulige!
// Eat supper

从上面的题目开始分析,按照上述的所要的输出结果:

  • 我们可以把LazyMan理解成为一个构造函数,在调用的时候输出参数内容
  • LazyMan这个函数支持链式调用
  • 这里面有几个方法:eat、sleep、sleepFirst
  • sleep这个方法比较特殊,链式调用将暂停一段时间之后继续执行,这里我们不难想到setTimeout这个方法
  • sleepFirst这个方法最特殊,这个方法的优先级最高;调用这个sleepFirst之后同样暂停一段时间继续执行,但是这个sleepFirst中的内容最先执行

按照上面的点我们继续分析一下:

  • 首先构造函数这个概念大家都不陌生,这里不做赘述。
  • 链式调用,链式调用这个概念,大家一般也都不陌生,我们之前最长用的链式调用应该是使用jQuery的时候,简单举个例子:$('#el').click().hide().show(),这个用起来很简单,但是笔者在面试的时候问到这个问题还是有很多人回答不上来。其实实现链式调用的原理很简单:那就是在我们函数的末尾执行的时候显式的返回this,这个this实际上就是我们这个调用的对象,this指向的问题可以拓展开来,但是在这里不做说明。
  • 关于上面的几个方法,我们不难发现LazyMan的一系列调用并不是按照顺序的,比如sleepFirst在调用的时候会优先执行;而且,我们的这些任务也不是同时执行的,所以,我们需要一个任务队列,这个任务队列执行各个任务
  • 我们有了任务队列之后,我们应该将相关的调用的方法加入到任务队列中,储存起来,后续统一调用,在写入任务队列的时候,我们需要注意sleepFirst这个方法,需要放到队列的最开始,作为优先执行的条件

代码实现

经过上面的分析,接下来我们可以编写我们的代码:

class LazyManGenerator {
    constructor(name) {
        //声明任务队列,这里放入将要执行的任务
        this.taskArray = [];
        const task = () => {
            console.log(`Hi, This is ${name}!`)
            //继续执行任务
            this.next()
        }
        //将初始任务放入到任务队列中
        this.taskArray.push(task);
        //这里是为了保证任务队列的正确执行,保证主线程任务也就是调用链执行完成之后开始执行任务队列的任务
        setTimeout(() => {
            this.next()
        },0)
    }
    next() {
        //取出任务并执行
        const task = this.taskArray.shift();
        task && task()
    }
    eat(name) {
        const task = () => {
            console.log(`Eat ${name}`)
            this.next();
        }
        this.taskArray.push(task)
        //显式的返回this,保证调用
        return this
    }
    sleepTask(num, isFirst) {
        
        const task = () => {
            console.log(`等待${num}秒...`)
            setTimeout(() => {
                console.log(`Wake up after ${num}`)
                this.next()
            }, num * 1000)
        }
        //判断是不是需要优先执行的任务,根据不同情况插入任务队列
        isFirst ? this.taskArray.unshift(task):this.taskArray.push(task)
    }
    sleep(num) {
        this.sleepTask(num, false)
        return this
    }
    sleepFirst(num) {
        this.sleepTask(num, true)
        return this
    }

}

function LazyMan(name) {
    return new LazyManGenerator(name)
}

我们简单分析一下上面的这个实现:

  • LazyMan方法返回一个LazyManGenerator构造函数的实例
  • 在LazyManGenerator constructor当中,我们维护了taskArray用来存储任务,同时将初始化任务放到taskArray当中
  • 还是在LazyManGenerator constructor中,将任务的逐个执行即next调用放在setTimeout当中,这样就能够保证在开始执行任务时,taskArray数组已经填满了任务
  • 我们来看next方法,取出taskArray数组中的首项,进行执行
  • eat方法将eat task放到taskArray当中,注意eat task方法需要调用this.next()显式调用下一个任务;同时返回this,完成链式调用
  • sleep和sleepFirst都调用了sleepTask,不同在第二个参数:sleepTask第二个参数保证是否优先执行,如果isFirst为true,则使用unshift将任务插到taskArray开头

其实上面的代码实现如果我们“稳住不慌”的情况还是能够一点一点完成实现的,但是往往我们再一看到这个问题的时候,就已经蒙了,所以根本无法实现上面的问题。

收获

接下来是我们在这道面试题中的收获,也是我认为最重要的东西,同时也是这个题目的考察点:

  • 面向对象的思想与设计,包括类的使用
  • 对象方法链式调用的理解和设计
  • 一些设计模式的思想
  • 一点代码的理解以及解耦和抽象能力
  • 逻辑的清晰程度和编程思想

 

说一下我自己的感受,很多的工程师对面试题的理解是:“当我需要换工作的时候,才去看面试题”。但是我认为,这种思维或者说是理解是错误的,很多的面试题,我们去看的时候,往往能够填补我们某一些方面的知识的空缺,将我们的所学串联起来,形成一种系统的编程逻辑思维,当然,这也对我们了解行业的现状有很大的帮助。还有,实际上,这也是说明我们在时刻准备着!

最后,感谢这些出题的大佬,费劲心思让我们了解更多的东西。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值