共 5024 字,讀完需 6 分鐘,速讀需 2 分鐘,本文首刊於知乎專欄前端週刊。寫在前面,筆者在做面試官這 2 年多的時間內,面試了數百個前端工程師,驚訝的發現,超過 80% 的候選人對下面這道題的回答情況連及格都達不到。這究竟是怎樣神奇的面試題呢?他考察了候選人的哪些能力?對正在讀本文的你有什麼啟示?且聽我慢慢道來(相關推薦《前端面試題》)
不起眼的開始
招募前端工程師,尤其是中高階前端工程師,紮實的JS 基礎絕對是必要條件,基礎不紮實的工程師在面對前端開發中的各種問題時大概率會束手無策。在考察候選人JS 基礎的時候,我經常會提供下面這段程式碼,然後讓候選人分析它實際運行的結果:
for (var i = 0; i <p>這段程式碼很短,只有7 行,我想,能讀到這裡的同學應該不需要我逐行解釋這段程式碼在做什麼吧。候選人面對這段程式碼時給出的結果也不盡相同,以下是典型的答案:</p>
- A. 20% 的人會快速掃描程式碼,然後給出結果:
0,1,2,3,4,5
; - B. 30% 的人會拿著程式碼逐行看,然後給出結果:
5,0,1, 2,3,4
; - C. 50% 的人會拿著程式碼仔細琢磨,然後給出結果:
5,5,5,5,5,5
;
只要你對JS 中同步與非同步程式碼的差異、變數作用域、閉包等概念有正確的理解,就知道正確答案是C,程式碼的實際輸出是:
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
接下來我會追問:如果我們約定,用箭頭表示其前後的兩次輸出之間有1 秒的時間間隔,而逗號表示其前後的兩次輸出之間的時間間隔可以忽略,程式碼實際運行的結果該如何描述?會有以下兩種答案:
- A. 60% 的人會描述為:
5 -> 5 -> 5 -> 5 -> 5
,即每個5 之間都有1 秒的時間間隔; - B. 40% 的人會被描述為:
5 -> 5,5,5,5,5
,即第1 個5 直接輸出,1 秒之後,輸出5 個5;
這就要求候選人對JS 中的定時器工作機制非常熟悉,循環執行過程中,幾乎同時設置了5 個定時器,一般情況下,這些定時器都會在1 秒之後觸發,而循環完的輸出是立即執行的,顯而易見,正確的描述是B。
如果到這裡算是及格的話,100 個人參加面試只有 20 人能及格,讀到這裡的同學可以仔細思考,你及格了?
追問1:閉包
如果這題只是考察候選人對JS 非同步程式碼、變數作用域的理解,限制未免太大,接下來我會追問,如果期望程式碼的輸出變成:5 -> 0,1,2,3,4
,該怎麼改造程式碼?熟悉閉包的同學很快能給出下面的解決方法:
for (var i = 0; i <p>巧妙的利用IIFE(Immediately Invoked Function Expression:聲明即執行的函數表達式)來解決閉包造成的問題,確實是不錯的思路,但是初學者可能不覺得這樣的程式碼很好懂,至少筆者初入門的時候這裡琢磨了一會兒才真正理解。 </p><p>有沒有更符合直覺的做法?答案是有,我們只需要對循環體稍做手腳,讓負責輸出的那段程式碼能拿到每次循環的 i 值。該怎麼做呢?利用JS 中基本型別(Primitive Type)的參數傳遞是按值傳遞(Pass by Value)的特徵,不難改造出下面的程式碼:</p><pre lang="js" class="">var output = function (i) {
setTimeout(function() {
console.log(new Date, i);
}, 1000);
};
for (var i = 0; i <p>能給出上述2 種解的候選人可以認為對JS 基礎的理解與運用是不錯的,可以各加10 分。當然實際面試中還有候選人給出如下的代碼:</p><pre lang="text" class="">for (let i = 0; i <p>細心的同學會發現,這裡只有個非常細微的變動,即使用ES6 塊級作用域(Block Scope)中的let 替代了var,但是程式碼在實際運行時會報錯,因為最後那個輸出使用的i 在其所在的作用域中並不存在,i 只存在於循環內部。 </p><p>能想到 ES6 特性的同學雖然沒有答對,但是展示了自己對 ES6 的了解,可以加 5 分,繼續進行下面的追問。 </p><h2 id="追問-ES">追問2:ES6</h2><p>有經驗的前端同學讀到這裡可能有些不耐煩了,扯了這麼多,都是他知道的內容,先別著急,挑戰的難度會繼續增加。 </p><p>接着上文继续追问:如果期望代码的输出变成 <code class="code">0 -> 1 -> 2 -> 3 -> 4 -> 5</code>,并且要求原有的代码块中的循环和两处 console.log 不变,该怎么改造代码?新的需求可以精确的描述为:代码执行时,立即输出 0,之后每隔 1 秒依次输出 1,2,3,4,循环结束后在大概第 5 秒的时候输出 5(这里使用大概,是为了避免钻牛角尖的同学陷进去,因为 JS 中的定时器触发时机有可能是不确定的,具体可参见 How Javascript Timers Work)。</p><p>看到这里,部分同学会给出下面的可行解:</p><pre lang="js" class="">for (var i = 0; i <p>不得不承认,这种做法虽粗暴有效,但是不算是能额外加分的方案。如果把这次的需求抽象为:在系列异步操作完成(每次循环都产生了 1 个异步操作)之后,再做其他的事情,代码该怎么组织?聪明的你是不是想起了什么?对,就是 Promise。</p><p>可能有的同学会问,不就是在控制台输出几个数字么?至于这样杀鸡用牛刀?你要知道,面试官真正想考察的是候选人是否具备某种能力和素质,因为在现代的前端开发中,处理异步的代码随处可见,熟悉和掌握异步操作的流程控制是成为合格开发者的基本功。</p><p>顺着下来,不难给出基于 Promise 的解决方案(既然 Promise 是 ES6 中的新特性,我们的新代码使用 ES6 编写是不是会更好?如果你这么写了,大概率会让面试官心生好感):</p><pre lang="js" class="">const tasks = [];
for (var i = 0; i {
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 {
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 <h2 id="总结">总结</h2><p>感谢你花时间读到这里,相信你收获的不仅仅是用 JS 精确控制代码输出的各种技巧,更是对于前端工程师的成长期许:扎实的语言基础、与时俱进的能力、强大技术自驱力。</p><p>推荐教程:《<a href="https://www.php.cn/js-tutorial.html" target="_blank">JS教程</a>》</p>
以上是超過80%的面試者不及格的JS 面試題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

選擇Python還是JavaScript取決於項目類型:1)數據科學和自動化任務選擇Python;2)前端和全棧開發選擇JavaScript。 Python因其在數據處理和自動化方面的強大庫而備受青睞,而JavaScript則因其在網頁交互和全棧開發中的優勢而不可或缺。

Python和JavaScript各有優勢,選擇取決於項目需求和個人偏好。 1.Python易學,語法簡潔,適用於數據科學和後端開發,但執行速度較慢。 2.JavaScript在前端開發中無處不在,異步編程能力強,Node.js使其適用於全棧開發,但語法可能複雜且易出錯。

javascriptisnotbuiltoncorc; sanInterpretedlanguagethatrunsonenginesoftenwritteninc.1)JavascriptwasdesignedAsignedAsalightWeight,drackendedlanguageforwebbrowsers.2)Enginesevolvedfromsimpleterterpretpretpretpretpreterterpretpretpretpretpretpretpretpretpretcompilerers,典型地,替代品。

JavaScript可用於前端和後端開發。前端通過DOM操作增強用戶體驗,後端通過Node.js處理服務器任務。 1.前端示例:改變網頁文本內容。 2.後端示例:創建Node.js服務器。

選擇Python還是JavaScript應基於職業發展、學習曲線和生態系統:1)職業發展:Python適合數據科學和後端開發,JavaScript適合前端和全棧開發。 2)學習曲線:Python語法簡潔,適合初學者;JavaScript語法靈活。 3)生態系統:Python有豐富的科學計算庫,JavaScript有強大的前端框架。

JavaScript框架的強大之處在於簡化開發、提升用戶體驗和應用性能。選擇框架時應考慮:1.項目規模和復雜度,2.團隊經驗,3.生態系統和社區支持。

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Dreamweaver Mac版
視覺化網頁開發工具

Dreamweaver CS6
視覺化網頁開發工具

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。