Maison  >  Article  >  interface Web  >  Questions d'entretien JS auxquelles plus de 80 % des personnes interrogées échouent

Questions d'entretien JS auxquelles plus de 80 % des personnes interrogées échouent

hzc
hzcavant
2020-06-28 09:57:314102parcourir

Un total de 5024 mots, il faut 6 minutes pour lire et 2 minutes pour accélérer la lecture. Cet article a été publié pour la première fois dans la colonne frontale de Zhihu. Comme écrit précédemment, l'auteur a interviewé des centaines d'ingénieurs front-end pendant plus de 2 ans en tant qu'intervieweur. J'ai été surpris de constater que plus de 80 % des candidats n'ont même pas répondu à la question suivante. De quel genre de question d’entretien magique s’agit-il ? Quelles capacités examine-t-il chez les candidats ? Quelles implications cela a-t-il pour vous qui lisez cet article ? Laissez-moi vous parler lentement (recommandation pertinente "Questions d'entretien front-end")

Humble début

Recrutement d'ingénieurs front-end, en particulier de milieu à Ingénieurs front-end seniors, une base JS solide est absolument nécessaire. Les ingénieurs avec une base faible seront très probablement impuissants face à divers problèmes de développement front-end. Lors de l'examen de la fondation JS du candidat, je fournis souvent le code suivant et je demande ensuite au candidat d'analyser les résultats de son fonctionnement réel :

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

Ce code est très court, seulement 7 lignes je pense, il peut les étudiants. En lisant ceci, je n'ai probablement pas besoin d'expliquer ligne par ligne ce que fait ce code. Les résultats donnés par les candidats face à ce code sont également différents. Voici les réponses typiques :

  • A 20% des personnes scanneront rapidement le code puis donneront le résultat : 0,1,2,3,4,5
  • B. 30% des gens prendront le code et le liront ligne par ligne, puis donneront le résultat : 5,0,1,2,3,4;
  • C 50% des gens prendront le code. et réfléchissez bien, puis le résultat est donné : 5,5,5,5,5,5;

Tant que vous avez une bonne compréhension de la différence entre le code synchrone et asynchrone en JS, la portée des variables, la fermeture et autres concepts, vous saurez que la bonne réponse est C, La sortie réelle du code est :

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

Ensuite, je demanderai : Si nous sommes d'accord, utilisez une flèche pour indiquer un intervalle de temps d'une seconde entre les deux sorties avant et après, et une virgule pour indiquer les deux sorties avant et après. L'intervalle de temps entre elles peut être ignoré. Comment décrire les résultats réels de l'exécution du code ? Il y aura les deux réponses suivantes :

  • A. 60% des gens le décriront comme : 5 -> 5 -> 5 -> 5 -> 5, c'est-à-dire qu'il y a un intervalle d'une seconde entre chaque B. 40% des personnes le décriront comme : </pre> <li>, c'est-à-dire que les 5 premiers sont sortis directement, et après 1 seconde, 5 5 sont sortis <code class="code">5 -> 5,5,5,5,5
  • Cela demande aux candidats de comprendre ; le timing dans JS Le mécanisme de fonctionnement du timer est très familier. Lors de l'exécution de la boucle, 5 timers sont réglés presque simultanément, dans des circonstances normales, ces timers seront déclenchés après 1 seconde et la sortie après l'exécution de la boucle est immédiate. . De toute évidence, la description correcte est B.

    Si vous êtes considéré comme qualifié ici, seules 20 personnes sur 100 réussiront l'entretien. Les étudiants qui lisent ceci peuvent réfléchir attentivement, avez-vous réussi ?

    Question suivante 1 : Clôture

    Si cette question vise simplement à tester la compréhension du candidat du code asynchrone JS et de la portée variable, les limites sont trop grandes. Ensuite, je demanderai si le candidat est attendu. la sortie du code devient :

    , comment modifier le code ? Les étudiants qui sont familiers avec les fermetures peuvent rapidement trouver les solutions suivantes : 5 -> 0,1,2,3,4

    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);

    Utilisez intelligemment IIFE (Immediately Invoked Function Expression : une expression de fonction déclarée et exécutée) pour résoudre les problèmes causés par les fermetures. en effet, c'est une bonne idée, mais les débutants ne trouveront peut-être pas un tel code facile à comprendre. Au moins quand j'ai commencé, j'y ai réfléchi pendant un moment avant de vraiment le comprendre.

    Existe-t-il une approche plus intuitive ? La réponse est oui, nous devons juste apporter quelques ajustements au corps de la boucle afin que le code responsable de la sortie puisse obtenir la valeur i de chaque boucle. Ce qu'il faut faire? Profitant de la fonctionnalité selon laquelle le paramètre passant du type primitif en JS est Pass by Value, il n'est pas difficile de transformer le code suivant :

    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);

    Candidats pouvant donner les deux solutions ci-dessus On peut considérer que la compréhension et l'application de base de JS sont bonnes et 10 points peuvent être ajoutés à chacun. Bien sûr, lors de l'entretien lui-même, certains candidats ont donné le code suivant :

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

    Les étudiants attentifs découvriront qu'il n'y a qu'un changement très subtil ici, qui consiste à utiliser let in ES6 block scope (Block Scope) à la place. . var est utilisé, mais le code signalera une erreur lors de son exécution réelle, car le i utilisé dans la dernière sortie n'existe pas dans la portée où il se trouve et i n'existe qu'à l'intérieur de la boucle.

    Les étudiants qui peuvent penser aux fonctionnalités d'ES6 peuvent ne pas répondre correctement, mais ils ont démontré leur compréhension d'ES6. Ils peuvent ajouter 5 points et continuer avec les questions suivantes.

    Question de suivi 2 : ES6

    Les étudiants front-end expérimentés peuvent être un peu impatients après avoir lu ceci. J'ai tellement parlé de ce qu'il sait. Ne vous inquiétez pas encore, le. la difficulté du défi continuera à augmenter.

    接着上文继续追问:如果期望代码的输出变成 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教程

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer