阿里面试题:实现一个EatMan。我没写出来🤦‍♂️

1,972 阅读5分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2 万元奖池等你挑战

背景

在今年一月份,很有幸参加了人生中的第一次阿里巴巴的面试,记忆尤新

在一面的时候,面试官没有过多的提问,一上来就是三道笔试题,一个小时完成

前两题是数组的题目,用了半个小时,就 A 出来了,当时写得挺爽,于是没有记录题目

在第三题,楼主就败北了,写了一半,就卡思路了,最后还是没能写完整 🙃。最后趁面试官不注意把问题记录一下了

这个题目在待办里边挂了整整半年,这几天花了挺多时间来研究这题,最后终于搞定,接下来就是原题目和我的解题过程(面对疾风吧!✨)

题目描述

实现一个EatMan
说明:实现一个EatManEatMan可以有以下一些行为
示例:
   1. EatMan('Hank')输出:
    Hi! This is Hank!
   2. EatMan('Hank').eat('dinner').eat('supper')输出
    Hi! This is Hank!
    Eat dinner~
    Eat supper~
   3. EatMan('Hank').eat('dinner').eatFirst('lunch')输出
    Eat lunch~
    Hi! This is Hank!
    Eat dinner~
   4. EatMan('Hank').eat('dinner').eatFirst('lunch').eatFirst('breakfast')输出
    Eat breakfast~
    Eat lunch~
    Hi! This is Hank!
    Eat dinner~

思路分析和手撕代码

(ps: 解题篇幅较长,希望大家耐心看完)

第一步:初步尝试🐱‍🏍

我们分析题目的第一个和第二个测试用例

 1. EatMan('Hank')输出
   Hi! This is Hank!
 2. EatMan('Hank').eat('dinner').eat('supper')输出
   Hi! This is Hank!
   Eat dinner~
   Eat supper~

思路分析

不难发现题目是想考察的是

  1. JavaScript 类的使用(为什么是类呢?因为我们可以把 eat这个方法看做是 EatMan 实例的一个属性)
  2. 链式调用的实现

我们尝试写一写代码

代码实现

/*
 * @Date: 2021-07-15 13:49:04
 * @LastEditors: cunhang_wwei
 * @LastEditTime: 2021-07-15 14:08:34
 * @Description: eatMan的初步尝试
 */

class MyEatMan {
    constructor(name) {
        this.name = name

        this.printName(this.name)
    }

    // 打印名字
    printName(name) {
        console.log(`Hi! This is ${name}!`)
    }

    eat(thing) {
        console.log(`Eat ${thing}~`)
        // 返回 this 传递指针实现链式调用
        return this
    }
}

// 实例化
function EatMan(name) {
    return new MyEatMan(name)
}
// 测试用例1
EatMan('Hank')
/**
 * 输出
 * Hi! This is Hank!
 */

// 测试用例2
EatMan('Hank').eat('dinner').eat('supper')
/**
 * 输出
 * Hi! This is Hank!
 * Eat dinner~
 * Eat supper~
 */

进行一下测试

image.png

image.png

发现返回了我们想要的答案

是不是有点小成就感了 😀,那我们继续!

第二步:进阶🐱‍👓

分析第三个和第四个测试用例

  3. EatMan('Hank').eat('dinner').eatFirst('lunch')输出
    Eat lunch~
    Hi! This is Hank!
    Eat dinner~
  4. EatMan('Hank').eat('dinner').eatFirst('lunch').eatFirst('breakfast')输出
    Eat breakfast~
    Eat lunch~
    Hi! This is Hank!
    Eat dinner~

思路分析

我们发现题目有了一个很大的改变,eatFirst会改变函数执行的顺序

有经验的大佬们一看到顺序,立马就悟了

对~ 考察的就是任务队列的知识

楼主之前就是在这个地方卡了思路,最后没有做出来 😅

我们得对我们第一步写的代码进行一个大改造,那就是增加任务队列

代码实现

class MyEatMan {
    constructor(name) {
        this.name = name
        // 任务队列,将需要执行的函数入队
        this.tasks = []
        // 第一个任务
        const task = this.printName(this.name)
        // 放入任务队列
        this.tasks.push(task)
        // 执行
        this.run()
    }

    // 打印名字
    printName(name) {
        return function() {
            console.log(`Hi! This is ${name}!`)
        }
    }

    // eat函数,每次调用都入队一个任务
    eat(thing) {
        const task = function() {
            console.log(`Eat ${thing}~`)
        }

        this.tasks.push(task)
        this.run()
        return this
    }

    // run执行任务
    run() {
        // 出队
        const currTask = this.tasks.shift()
        // 执行
        currTask && currTask()
    }
}

进行一下测试

// 测试用例1
EatMan('Hank')
/**
 * 输出
 * Hi! This is Hank!
 */
// 测试用例2
EatMan('Hank').eat('dinner').eat('supper')
/**
 * 输出
 * Hi! This is Hank!
 * Eat dinner~
 * Eat supper~
 */

image.png

发现输出结果和答案一致,那说明我们的改造是 OK 的

第三步:完善👏

最后,我们来实现eatFirst函数

思路分析

  1. eatFirst函数有插队的功能,执行到了eatFirst之后,才能把该任务插入到任务队列的队头
  2. new MyEatMan() 的时候不能立即执行任务队列,而是在任务进队完毕之后才执行
  3. 每一个任务执行完毕的时候,触发下一个任务,保证任务的连续性

代码实现

class MyEatMan {
    constructor(name) {
        this.name = name
        // 任务队列,将需要执行的函数入队
        this.tasks = []
        // 第一个任务
        const task = this.printName(this.name)
        // 放入任务队列
        this.tasks.push(task)
        // 为了保证任务都能在进队完毕之后再执行,创建一个宏任务,让执行任务的时机放到 下一个事件循环里
        let self = this
        setTimeout(function () {
            // console.log('tasks', self.tasks)
            self.run()
        }, 0)
    }

    // 打印名字
    printName(name) {
        let self = this
        return function () {
            console.log(`Hi! This is ${name}!`)
            self.run()
        }
    }

    // eat函数,每次调用都入队一个任务,而且还能实现链式调用
    eat(thing) {
        let self = this
        const task = function () {
            console.log(`Eat ${thing}~`)
            self.run()
        }

        this.tasks.push(task)
        return this
    }

    // eatFirst函数,谁最后初始化,谁先执行,而且还能实现链式调用
    eatFirst(thing) {
        let self = this
        const task = function () {
            console.log(`Eat ${thing}~`)
            self.run()
        }

        // 插入到队列的头部
        this.tasks.unshift(task)
        return this
    }

    // run执行任务
    run() {
        // 出队
        const currTask = this.tasks.shift()
        // 执行
        currTask && currTask()
    }
}


function EatMan(name) {
    return new MyEatMan(name)
}

我们使用测试用例进行测试

// 测试用例3

EatMan('Hank').eat('dinner').eatFirst('lunch')
/**
 * 输出
 * Eat lunch~
 * Hi! This is Hank!
 * Eat dinner~
 */

image.png

// 测试用例4
EatMan('Hank').eat('dinner').eatFirst('lunch').eatFirst('breakfast')
/**
 * 输出
 * Eat breakfast~
 * Eat lunch~
 * Hi! This is Hank!
 * Eat dinner~
 */

image.png

输出结果和答案完全一致!

大工告成了!

最后

关于面试,面试官大发慈悲,让我进入了二面,但是自己当时没有充分地准备,回答得不是很好,二面就挂掉了🤦‍♂️

希望大家看完这个文章都有所收获,如果能帮助屏幕前的你,解决了这个题目,那就最好不过了

我是970,好好学习不会差,大家一起进步!

最后的最后,求内推!地点深圳,希望大佬们积极留言啊!