Home  >  Article  >  Web Front-end  >  JS interview questions that more than 80% of interviewees fail

JS interview questions that more than 80% of interviewees fail

hzc
hzcforward
2020-06-28 09:57:314119browse

There are 5024 words in total. It takes 6 minutes to finish reading and 2 minutes to speed read. This article was first published in Zhihu column front-end weekly. As written before, the author has interviewed hundreds of front-end engineers during more than 2 years as an interviewer. I was surprised to find that more than 80% of the candidates did not even pass the answer to the following question. What kind of magical interview question is this? What abilities does he examine in candidates? What implications does it have for you who are reading this article? Let me tell you slowly (Related recommendation " Front-end Interview Questions")

Humble Beginning

Recruiting front-end engineers, especially mid-to-senior front-end Engineers, a solid JS foundation is absolutely necessary. Engineers with a weak foundation will most likely be helpless when facing various problems in front-end development. When examining the candidate's JS foundation, I often provide the following code and then ask the candidate to analyze the results of its actual operation:

for (var i = 0; i < 5; i++) {
    setTimeout(function() {        
        console.log(new Date, i);
    }, 1000);
}
console.log(new Date, i);

This code is very short, only 7 lines. I think, it can Students reading this probably don’t need me to explain line by line what this code is doing. The results given by candidates when faced with this code are also different. The following is a typical answer:

  • A. 20% of people will quickly scan the code and then give the result: 0,1,2,3,4,5
  • B. 30% of people will take the code and look at it line by line, and then give the result: 5,0,1, 2,3,4;
  • C. 50% of people will take the code and think about it carefully, and then give the result: 5,5,5,5,5,5 ;

As long as you have a correct understanding of the difference between synchronous and asynchronous code, variable scope, closure and other concepts in JS, you will know that the correct answer is C, and the actual output of the code is:

2017-03-18T00:43:45.873Z 5
2017-03-18T00:43:46.866Z 5
2017-03-18T00:43:46.868Z 5
2017-03-18T00:43:46.868Z 5
2017-03-18T00:43:46.868Z 5
2017-03-18T00:43:46.868Z 5

Next I will ask: If we agree that the arrow indicates that there is a 1 second time interval between the two outputs before and after, and the comma indicates that the time interval between the two outputs before and after it can be ignored, How to describe the actual results of running the code? There will be the following two answers:

  • A. 60% of people will describe it as: 5 -> 5 -> 5 -> 5 -> 5, That is, there is a time interval of 1 second between each 5; </pre> <li>B. 40% of people will describe it as: <code class="code">5 -> 5,5,5,5,5, That is, the first 5 is output directly, and after 1 second, 5 5s are output;
  • This requires the candidate to be very familiar with the timer working mechanism in JS. During the loop execution, it is set almost simultaneously There are 5 timers. Under normal circumstances, these timers will be triggered after 1 second, and the output after the loop is executed immediately. Obviously, the correct description is B.

    If you are considered qualified here, only 20 of 100 people will pass the interview. Students who read this can think carefully, did you pass?

    Question 1: Closure

    If this question is just to test the candidate’s understanding of JS asynchronous code and variable scope, the limitations are too great. Next, I will ask, if The expected output of the code becomes: 5 -> 0,1,2,3,4. How to modify the code? Students who are familiar with closures can quickly give the following solutions:

    for (var i = 0; i < 5; i++) {
        (function(j) {  // j = i
            setTimeout(function() {            
                console.log(new Date, j);
            }, 1000);
        })(i);
    }console.log(new Date, i);

    Cleverly use IIFE (Immediately Invoked Function Expression: a function expression that is declared and executed) to solve the problems caused by closures. It is indeed A good idea, but beginners may not find such code easy to understand. At least when I first started, I thought about it for a while before I really understood it.

    Is there a more intuitive approach? The answer is yes, we just need to make some adjustments to the loop body so that the code responsible for output can get the i value of each loop. how should I do it? Taking advantage of the feature that the parameter passing of the basic type (Primitive Type) in JS is Pass by Value, it is not difficult to transform the following code:

    var output = function (i) {
        setTimeout(function() {        
            console.log(new Date, i);
        }, 1000);
    };
    for (var i = 0; i < 5; i++) {
        output(i);  // 这里传过去的 i 值被复制了
    }
    console.log(new Date, i);

    can give candidates for the above two solutions It can be considered that the basic understanding and application of JS are good, and 10 points can be added to each. Of course, in the actual interview, some candidates gave the following code:

    for (let i = 0; i < 5; i++) {
        setTimeout(function() {        
            console.log(new Date, i);
        }, 1000);
    }
    console.log(new Date, i);

    Careful students will find that there is only a very subtle change here, which is to use let in the ES6 block scope (Block Scope) instead. var is used, but the code will report an error when it is actually run, because the i used in the last output does not exist in the scope where it is located, and i only exists inside the loop.

    Students who can think of ES6 features may not answer correctly, but have demonstrated their understanding of ES6. They can add 5 points and continue with the following questions.

    Following question 2: ES6

    Experienced front-end students may be a little impatient after reading this. I have talked so much, and it is all what he knows. Don’t worry, the difficulty of the challenge will be Continue growing.

    接着上文继续追问:如果期望代码的输出变成 0 -> 1 -> 2 -> 3 -> 4 -> 5,并且要求原有的代码块中的循环和两处 console.log 不变,该怎么改造代码?新的需求可以精确的描述为:代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,循环结束后在大概第 5 秒的时候输出 5(这里使用大概,是为了避免钻牛角尖的同学陷进去,因为 JS 中的定时器触发时机有可能是不确定的,具体可参见 How Javascript Timers Work)。

    看到这里,部分同学会给出下面的可行解:

    for (var i = 0; i < 5; i++) {
        (function(j) {
            setTimeout(function() {            
                console.log(new Date, j);
            }, 1000 * j);  // 这里修改 0~4 的定时器时间
        })(i);
    }
    setTimeout(function() { 
            // 这里增加定时器,超时设置为 5 秒
        console.log(new Date, i);
    }, 1000 * i);

    不得不承认,这种做法虽粗暴有效,但是不算是能额外加分的方案。如果把这次的需求抽象为:在系列异步操作完成(每次循环都产生了 1 个异步操作)之后,再做其他的事情,代码该怎么组织?聪明的你是不是想起了什么?对,就是 Promise。

    可能有的同学会问,不就是在控制台输出几个数字么?至于这样杀鸡用牛刀?你要知道,面试官真正想考察的是候选人是否具备某种能力和素质,因为在现代的前端开发中,处理异步的代码随处可见,熟悉和掌握异步操作的流程控制是成为合格开发者的基本功。

    顺着下来,不难给出基于 Promise 的解决方案(既然 Promise 是 ES6 中的新特性,我们的新代码使用 ES6 编写是不是会更好?如果你这么写了,大概率会让面试官心生好感):

    const tasks = [];
    for (var i = 0; i < 5; i++) {   // 这里 i 的声明不能改成 let,如果要改该怎么做?
        ((j) => {
            tasks.push(new Promise((resolve) => {
                setTimeout(() => {
                    console.log(new Date, j);
                    resolve();  // 这里一定要 resolve,否则代码不会按预期 work
                }, 1000 * j);   // 定时器的超时时间逐步增加
            }));
        })(i);
    }
    
    Promise.all(tasks).then(() => {
        setTimeout(() => {
            console.log(new Date, i);
        }, 1000);   // 注意这里只需要把超时设置为 1 秒
    });

    相比而言,笔者更倾向于下面这样看起来更简洁的代码,要知道编程风格也是很多面试官重点考察的点,代码阅读时的颗粒度更小,模块化更好,无疑会是加分点。

    const tasks = []; // 这里存放异步操作的 Promise
    const output = (i) => new Promise((resolve) => {
        setTimeout(() => {
            console.log(new Date, i);
            resolve();
        }, 1000 * i);
    });
    
    // 生成全部的异步操作
    for (var i = 0; i < 5; i++) {
        tasks.push(output(i));
    }
    
    // 异步操作完成之后,输出最后的 i
    Promise.all(tasks).then(() => {
        setTimeout(() => {
            console.log(new Date, i);
        }, 1000);
    });

    读到这里的同学,恭喜你,你下次面试遇到类似的问题,至少能拿到 80 分。

    我们都知道使用 Promise 处理异步代码比回调机制让代码可读性更高,但是使用 Promise 的问题也很明显,即如果没有处理 Promise 的 reject,会导致错误被丢进黑洞,好在新版的 Chrome 和 Node 7.x 能对未处理的异常给出 Unhandled Rejection Warning,而排查这些错误还需要一些特别的技巧(浏览器、Node.js)。

    追问 3:ES7

    既然你都看到这里了,那就再坚持 2 分钟,接下来的内容会让你明白你的坚持是值得的。

    多数面试官在决定聘用某个候选人之前还需要考察另外一项重要能力,即技术自驱力,直白的说就是候选人像有内部的马达在驱动他,用漂亮的方式解决工程领域的问题,不断的跟随业务和技术变得越来越牛逼,究竟什么是牛逼?建议阅读程序人生的这篇剖析。

    回到正题,既然 Promise 已经被拿下,如何使用 ES7 中的 async await 特性来让这段代码变的更简洁?你是否能够根据自己目前掌握的知识给出答案?请在这里暂停 1 分钟,思考下。

    下面是笔者给出的参考代码:

    // 模拟其他语言中的 sleep,实际上可以是任何异步操作
    const sleep = (timeountMS) => new Promise((resolve) => {
        setTimeout(resolve, timeountMS);
    });
    
    (async () => {  // 声明即执行的 async 函数表达式
        for (var i = 0; i < 5; i++) {
            await sleep(1000);
            console.log(new Date, i);
        }
    
        await sleep(1000);
        console.log(new Date, i);
    })();

    总结

    感谢你花时间读到这里,相信你收获的不仅仅是用 JS 精确控制代码输出的各种技巧,更是对于前端工程师的成长期许:扎实的语言基础、与时俱进的能力、强大技术自驱力。

    推荐教程:《JS教程

    The above is the detailed content of JS interview questions that more than 80% of interviewees fail. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete