面试官想要什么答案?结合chatGPT或许可以告诉你🔥

374 阅读10分钟

前言

逢金三银四无论是招人的还是找工作的都忙得不行,本人所在部门也需要招一个中高级前端工程师。过去几个月也面了几十个候选人了,其中大部分都在面试过程被淘汰掉。有个别候选人通过了最终面试,但是因为地域问题或者拿到更适合的offer把我鸽了,是真的难受。

image.png

面试嘛,其实就是考察同学们学习的能力怎么样。

教员的《矛盾论》讲到“矛盾是事物发展的内部原因”、“矛盾是存在的、普遍的、不可避免的、相互依存的”,人普通存在知识的输入和知识的输出不匹配的矛盾。 因此要想轻松拿到offer,不仅要会知识输入,还要会知识输出,并且要“抓住主要矛盾的主要方面”。

image.png

候选人要知道面试官想考什么,自己该输出什么知识。

面试官是怎么想的

面试官通常会考察这些方面:

  1. 知识面广度:这东西了解过没?
  2. 垂直考察:想东西够不够深入?有没有钻研精神?
  3. 横向考察:想东西是不是结构化的?能不能举一反三?
  4. 应用能力:懂不懂得应用?能够结合所知形成解决方案?

下面以Promise这个考点为例,抽丝剥茧,一层层剖析这个玩意

这东西了解过没?

知识面广度考察:Promise是什么?这东西了解过没?

最近chatGPT不是很火吗?我们一起看看它怎么说的? 第一个问题:Javascript编程语言中的Promise是什么? image.png

Promise是一种用于异步编程的语言特性,可以避免传统的回调地狱(callback hell)问题。

通过Promise,我们可以更直观地处理异步流程,将异步操作的返回结果进行链式处理,提高代码的可读性和可维护性。

在JavaScript中,Promise是一种表示异步操作的对象,代表一个“承诺”(promise),表示在未来某个时间点会返回一个结果。

Promise对象具有三种状态:

  1. pending: 初始状态,异步操作尚未完成
  2. fulfilled: 异步操作成功返回结果
  3. rejected: 异步操作失败,返回错误信息 通过链式调用 .then()方法,我们可以在Promise对象返回结果后执行相应的回调函数,而在中途发生错误或异常时,也可以以 .catch()方法捕获错误信息,进行相应的处理。

我觉得这个回答不太容易理解,后面我又继续问他,“用最容易理解的话讲Javascript编程语言中的Promise是什么?”

image.png

Promise是Javascript编程语言中一种处理异步操作的方式,它可以用来处理需要等待一段时间才能得到结果的任务。Promise的主要作用是在异步操作完成后,通知代 码执行相应的处理操作,而不是在异步操作执行期间一直等待结果。可以将Promise理解为一种容器,可以通过该容器来获取异步操作的结果并进行处理。使用Promise 可以使代码更加简洁、可读性更高。

还是太枯燥了,要幽默风趣一点

image.png

嘿,小伙伴们!你们熟悉Javascript编程语言中的Promise吗? 如果不熟悉也没关系,我来为大家简单讲解一下。首先,Promise可以理解为“承诺”,就像我们平时跟朋 友之间约定了看电影、吃烧烤等活动一样。在程序中,Promise也是类似的概念。我们可以把一个耗时较长的操作包装在一个“承诺”里,Promise会在后台帮我们等 待“承诺”实现。当“承诺”实现时,Promise会将结果返回给程序。比如说,我们可以使用Promise来处理Ajax请求。当我们向服务器发送请求时,程序会立即执行下一 行代码,而Promise会在后台等待服务器响应。当服务器返回结果后,Promise会将结果传递给程序,让我们轻松地处理数据。总之,Promise就是程序中的“朋友”,承 载着我们对异步操作的期望。而我们则可以轻松依赖这个“朋友”,跟他一起享受编程的乐趣!

image.png

这回答差点没把我笑死,这个问题大体到位即可,让面试官知道你了解过。

想东西够不够深入?有没有钻研精神?

如果你会用了,到底知不知道背后怎么实现的,有没有寻根问底。大家可能会问,这东西能用得上吗?其实这个东西一方面能够提升你的编程能力,形成更佳的编程思维。另一方面,企业招聘面试无非是为了择优,现在实在是太卷了,需要提高门槛才能筛选出合适的人才,面试和被面试的人都很难受。

垂直考察:可以手写一个Promise吗?

这里给个参考:

class MyPromise {
    PENDDING = 'PENDDING';
    FULLFILLED = 'FULLFILLED';
    REJECTED = 'REJECTED';
    resFns: any = [];
    rejFns: any = [];
    status = this.PENDDING;
    result;
    key = Date.now();

    static resolve(value) {
        return new MyPromise((resolve) => resolve(value));
    }

    static reject(reason) {
        return new MyPromise((_, reject) => reject(reason));
    }

    _resolve = (value) => {
        setTimeout(() => {
            if (this.status === this.PENDDING) {
                this.status = this.FULLFILLED;
                this.result = value;
                this.resFns.forEach(
                    ({ fn }) => (this.result = fn(this.result))
                );
            }
        });
    };

    _reject = (reason) => {
        setTimeout(() => {
            if (this.status === this.PENDDING) {
                this.status = this.REJECTED;
                this.result = reason;
                this.rejFns.forEach(({ fn }) => (this.result = fn(reason)));
            }
        });
    };

    constructor(_fn) {
        try {
            _fn(this._resolve, this._reject);
        } catch (e) {
            MyPromise.reject(e);
        }
    }

    then(resFn, rejFn?) {
        if (this.status === this.PENDDING) {
            // const instance = new MyPromise((resolve, reject) => {
            if (resFn)
                this.resFns.push({
                    fn: resFn,
                    resolve: this._resolve,
                    reject: this._reject,
                });
            if (rejFn)
                this.rejFns.push({
                    fn: rejFn,
                    resolve: this._resolve,
                    reject: this._reject,
                });
            // })
            return this;
        }
        if (this.status === this.FULLFILLED) {
            this.result = resFn(this.result);
            return this;
        }

        if (this.status === this.REJECTED) {
            this.result = rejFn(this.result);
            return this;
        }
    }

    catch(rejFn) {
        return this.then(undefined, rejFn);
    }

    finally(cb) {
        return this.then(cb, cb);
    }
}

如果候选人可以写出来,此时面试官👇

image.png

内心OS:“底子还是可以的,能不能再手写一个Promise.all?”

垂直考察:可以手写一个 Promise.all吗?

参考代码

function PromiseAll(promArr) {
    const resolveArr: any[] = [];
    return new Promise((resolve, reject) => {
        Array.from(promArr).forEach((el) => {
            Promise.resolve(el)
                .then((value) => {
                    resolveArr.push(value);
                    if (resolveArr.length >= promArr.length) {
                        resolve(resolveArr);
                    }
                })
                .catch((reason) => {
                    reject(reason);
                });
        });
    });
}

当然类似的API有很多,也可能会考下面这道题。

垂直考察:可以手写一个 Promise.any 吗?

参考答案:

function PromiseAny(promArr) {
    const rejectArr: any[] = [];
    return new Promise((resolve, reject) => {
        Array.from(promArr).forEach((item) =>
            Promise.resolve(item)
                .then((val) => {
                    resolve(val);
                })
                .catch((reason: Error) => {
                    rejectArr.push(reason);
                    if (rejectArr.length >= promArr.length) {
                        // @ts-ignore
                        reject(new AggregateError(rejectArr));
                    }
                })
        );
    });
}

想东西是不是结构化的?能不能举一反三?

不同的同学思维结构不一样,有的同学像单链表,比如JS相关知识是一条链路,浏览器相关知识又是一条链路。 有的是树状的,有的是像高速马路一样互相联系。其实知识之间关联越多越好,推荐大家看看《如何高效学习》这本书。

image.png

横向考察:Promise.any和Promise.race的区别

我们在学习的时候,可以多横向想想相似的东西有什么。Promise.any相似的API有很多,比如Promise.race就是其中一个。你说你知道,那么面试官就会问“他俩有什么区别?”。这里偷懒一下,继续问了问chatGPT。 image.png 感觉还是挺全的。

  1. any()方法接收任意数量的Promise对象,只要有一个Promise对象的状态变成fulfilled,就会返回该Promise的结果值;若所有的Promise都rejected,则返回一个AggregateError对象,其中包含了所有Promise对象的rejected原因。而race()方法也是接收任意数量的Promise对象,但是它只会返回最先resolved或rejected的Promise对象的值或原因,不管其它的Promise的状态如何。
  2. 当Promise.any()方法返回fulfilled状态时,其中一个Promise的结果就被传递给了回调函数,不过返回值只有一组数据;而当Promise.race()方法返回fulfilled状态时,其中的一个Promise的结果也被传递给了回调函数,但此时只有一个结果。
  3. any()方法会用Promise.allSettled()方法来实现,而race()方法只用单纯的竞速来实现,如果成功就执行resolve,如果失败就执行reject。

总的来说,Promise.any()方法是在多个Promise同时执行时,只需要其中的一个Promise被成功(resolve)处理,就立即返回resolve,不过如果所有的Promise都被拒绝(rejected),则会返回AggregateError集合。而Promise.race()方法,只要任意一个Promise成功(resolve)或者失败(reject)就会立即返回,不过返回值只有一个(无论成功或失败),所以,它要么只返回一个成功值,要么只返回一个失败值。

横向考察:异步任务之间的不同与相同点

我们可以尝试抽象一下:Promise的执行是异步任务,有没有其它的异步任务了?

image.png

答案是有的。 image.png

Javascript的异步任务包括:

  1. 定时器(setTimeout、setInterval);
  2. 回调函数;
  3. Promise;
  4. Generator;
  5. async/await;
  6. XMLHttpRequest和fetch;
  7. WebWorkers;
  8. 事件监听器;
  9. Node.js中的处理异步IO的模块(fs、net、http等模块)。 以上是常见的异步任务,还有其他的一些方法,如setTimeout是浏览器提供的,而Promise和async/await是ES6新增的语法特性。不同的异步任务适用于不同的场景,可以根据具体的需求来选择合适的方式。

我还问了他们之间的相同点和不同点: image.png

相同点:

  1. 都不会阻塞代码的执行,提高了程序的效率。
  2. 执行结果通常需要回调函数或Promise对象的处理。 不同点:
  3. 不同的异步任务需要不同的处理方式,例如事件监听器和回调函数需要在不同的上下文中处理各种事件和其他操作。 2. 一些异步任务会返回数据,例如Ajax请求和Promise对象,而定时器和事件监听器则通常没有返回任何值。
  4. 异步任务之间的执行顺序通常是不确定的,例如回调函数和事件监听器的顺序是由事件触发的顺序决定的。

如果再深入的话,可以思考一下:他们一起执行的话,有没有优先级问题?这时候就涉及到事件循环了,后面我会写文章展开讲讲。

懂不懂得应用?能够结合所知形成解决方案?

面试官往往很害怕考神型候选人,题目倒背如流,最后招进来动手能力不够,不匹配部门需求。所以,面试官都会考察一下能不能利用已有知识形成解决方案。

image.png

Promise任务是异步任务,那么异步任务就有并发和阻塞的问题。如果想要控制并发量怎么样?出一个这样的题目,会怎么做?

实现一个 promise.map,进行并发数控制,有以下测试用例

pMap([1, 2, 3, 4, 5], (x) => Promise.resolve(x + 1));

pMap([Promise.resolve(1), Promise.resolve(2)], (x) => x + 1);

// 注意输出时间控制
pMap([1, 1, 1, 1, 1, 1, 1, 1], (x) => sleep(1000), { concurrency: 2 });

image.png

会不会觉得有点懵?我给下参考代码:

function pMap([...list], mapper, { concurrency = Infinity } = {}) {
    // TODO : type
    let queue: any[] = [];
    let results: any[] = [];
    return new Promise((resolve) => {
        function proceed() {
            const times = concurrency - queue.length;

            for (let i = 0; i < times; i++) {
                if (!list.length) return;

                const promise = Promise.resolve(list.shift())
                    .then(mapper)
                    .then((data: any) => results.push(data))
                    .finally(() => {
                        queue = queue.filter((item) => promise !== item);
                        if (!queue.length && !list.length)
                            return resolve(results);
                        return proceed();
                    });

                queue.push(promise);
            }
        }

        proceed();
    });
}

文章内容基本结束,觉得内容还可以的话可以收藏+点赞,最后我再总结一下吧。

总结

  1. 面试官通常会考察以下几个方面
  • 知识面广度:这东西了解过没?
  • 垂直考察:想东西够不够深入?有没有钻研精神?
  • 横向考察:想东西是不是结构化的?能不能举一反三?
  • 应用能力:懂不懂得应用?能不能结合其他技术形成解决方案?
  1. 需要懂得垂直深入,形成寻根问底的好习惯
  2. 需要懂的横向比较,可以通过抽象的方法横向联想一下关联、相似的知识
  3. 需要懂得应用,思考其应用场景,结合所知形成解决方案