80% 應(yīng)聘者都不及格的 JS 面試題

共 5024 字酝掩,讀完需 6 分鐘鳞芙,速讀需 2 分鐘,首發(fā)于知乎專欄前端周刊期虾。寫在前面原朝,筆者在做面試官這 2 年多的時(shí)間內(nèi),面試了數(shù)百個(gè)前端工程師镶苞,驚訝的發(fā)現(xiàn)喳坠,超過 80% 的候選人對(duì)下面這道題的回答情況連及格都達(dá)不到。這究竟是怎樣神奇的一道面試題茂蚓?他考察了候選人的哪些能力壕鹉?對(duì)正在讀本文的你有什么啟示剃幌?且聽我慢慢道來

不起眼的開始

招聘前端工程師,尤其是中高級(jí)前端工程師晾浴,扎實(shí)的 JS 基礎(chǔ)絕對(duì)是必要條件锥忿,基礎(chǔ)不扎實(shí)的工程師在面對(duì)前端開發(fā)中的各種問題時(shí)大概率會(huì)束手無策。在考察候選人 JS 基礎(chǔ)的時(shí)候怠肋,我經(jīng)常會(huì)提供下面這段代碼敬鬓,然后讓候選人分析它實(shí)際運(yùn)行的結(jié)果:

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

console.log(new Date, i);

這段代碼很短,只有 7 行笙各,我想钉答,能讀到這里的同學(xué)應(yīng)該不需要我逐行解釋這段代碼在做什么吧。候選人面對(duì)這段代碼時(shí)給出的結(jié)果也不盡相同杈抢,以下是典型的答案:

  • A. 20% 的人會(huì)快速掃描代碼数尿,然后給出結(jié)果:0,1,2,3,4,5
  • B. 30% 的人會(huì)拿著代碼逐行看惶楼,然后給出結(jié)果:5,0,1,2,3,4右蹦;
  • C. 50% 的人會(huì)拿著代碼仔細(xì)琢磨,然后給出結(jié)果:5,5,5,5,5,5歼捐;

只要你對(duì) JS 中同步和異步代碼的區(qū)別何陆、變量作用域、閉包等概念有正確的理解豹储,就知道正確答案是 C贷盲,代碼的實(shí)際輸出是:

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

接下來我會(huì)追問:如果我們約定,用箭頭表示其前后的兩次輸出之間有 1 秒的時(shí)間間隔剥扣,而逗號(hào)表示其前后的兩次輸出之間的時(shí)間間隔可以忽略巩剖,代碼實(shí)際運(yùn)行的結(jié)果該如何描述?會(huì)有下面兩種答案:

  • A. 60% 的人會(huì)描述為:5 -> 5 -> 5 -> 5 -> 5钠怯,即每個(gè) 5 之間都有 1 秒的時(shí)間間隔佳魔;
  • B. 40% 的人會(huì)描述為:5 -> 5,5,5,5,5,即第 1 個(gè) 5 直接輸出晦炊,1 秒之后鞠鲜,輸出 5 個(gè) 5;

這就要求候選人對(duì) JS 中的定時(shí)器工作機(jī)制非常熟悉刽锤,循環(huán)執(zhí)行過程中镊尺,幾乎同時(shí)設(shè)置了 5 個(gè)定時(shí)器,一般情況下并思,這些定時(shí)器都會(huì)在 1 秒之后觸發(fā)庐氮,而循環(huán)完的輸出是立即執(zhí)行的,顯而易見宋彼,正確的描述是 B弄砍。

如果到這里算是及格的話仙畦,100 個(gè)人參加面試只有 20 人能及格,讀到這里的同學(xué)可以仔細(xì)思考音婶,你及格了么慨畸?

追問 1:閉包

如果這道題僅僅是考察候選人對(duì) JS 異步代碼、變量作用域的理解衣式,局限性未免太大寸士,接下來我會(huì)追問,如果期望代碼的輸出變成:5 -> 0,1,2,3,4碴卧,該怎么改造代碼弱卡?熟悉閉包的同學(xué)很快能給出下面的解決辦法:

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

巧妙的利用 IIFE(Immediately Invoked Function Expression:聲明即執(zhí)行的函數(shù)表達(dá)式)來解決閉包造成的問題,確實(shí)是不錯(cuò)的思路住册,但是初學(xué)者可能并不覺得這樣的代碼很好懂婶博,至少筆者初入門的時(shí)候這里琢磨了一會(huì)兒才真正理解。

有沒有更符合直覺的做法荧飞?答案是有凡人,我們只需要對(duì)循環(huán)體稍做手腳,讓負(fù)責(zé)輸出的那段代碼能拿到每次循環(huán)的 i 值即可叹阔。該怎么做呢挠轴?利用 JS 中基本類型(Primitive Type)的參數(shù)傳遞是按值傳遞(Pass by Value)的特征,不難改造出下面的代碼:

var output = function (i) {
    setTimeout(function() {
        console.log(new Date, i);
    }, 1000);
};

for (var i = 0; i < 5; i++) {
    output(i);  // 這里傳過去的 i 值被復(fù)制了
}

console.log(new Date, i);

能給出上述 2 種解決方案的候選人可以認(rèn)為對(duì) JS 基礎(chǔ)的理解和運(yùn)用是不錯(cuò)的条获,可以各加 10 分忠荞。當(dāng)然實(shí)際面試中還有候選人給出如下的代碼:

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

console.log(new Date, i);

細(xì)心的同學(xué)會(huì)發(fā)現(xiàn),這里只有個(gè)非常細(xì)微的變動(dòng)帅掘,即使用 ES6 塊級(jí)作用域(Block Scope)中的 let 替代了 var,但是代碼在實(shí)際運(yùn)行時(shí)會(huì)報(bào)錯(cuò)堂油,因?yàn)樽詈竽莻€(gè)輸出使用的 i 在其所在的作用域中并不存在修档,i 只存在于循環(huán)內(nèi)部。

能想到 ES6 特性的同學(xué)雖然沒有答對(duì)府框,但是展示了自己對(duì) ES6 的了解吱窝,可以加 5 分,繼續(xù)進(jìn)行下面的追問迫靖。

追問 2:ES6

有經(jīng)驗(yàn)的前端同學(xué)讀到這里可能有些不耐煩了院峡,扯了這么多,都是他知道的內(nèi)容系宜,先別著急照激,挑戰(zhàn)的難度會(huì)繼續(xù)增加。

接著上文繼續(xù)追問:如果期望代碼的輸出變成 0 -> 1 -> 2 -> 3 -> 4 -> 5盹牧,并且要求原有的代碼塊中的循環(huán)和兩處 console.log 不變俩垃,該怎么改造代碼励幼?新的需求可以精確的描述為:代碼執(zhí)行時(shí),立即輸出 0口柳,之后每隔 1 秒依次輸出 1,2,3,4苹粟,循環(huán)結(jié)束后在大概第 5 秒的時(shí)候輸出 5(這里使用大概,是為了避免鉆牛角尖的同學(xué)陷進(jìn)去跃闹,因?yàn)?JS 中的定時(shí)器觸發(fā)時(shí)機(jī)有可能是不確定的嵌削,具體可參見 How Javascript Timers Work)。

看到這里望艺,部分同學(xué)會(huì)給出下面的可行解:

for (var i = 0; i < 5; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(new Date, j);
        }, 1000 * j));  // 這里修改 0~4 的定時(shí)器時(shí)間
    })(i);
}

setTimeout(function() { // 這里增加定時(shí)器掷贾,超時(shí)設(shè)置為 5 秒
    console.log(new Date, i);
}, 1000 * i));

不得不承認(rèn),這種做法雖粗暴有效荣茫,但是不算是能額外加分的方案想帅。如果把這次的需求抽象為:在系列異步操作完成(每次循環(huán)都產(chǎn)生了 1 個(gè)異步操作)之后,再做其他的事情啡莉,代碼該怎么組織港准?聰明的你是不是想起了什么?對(duì)咧欣,就是 Promise浅缸。

可能有的同學(xué)會(huì)問,不就是在控制臺(tái)輸出幾個(gè)數(shù)字么魄咕?至于這樣殺雞用牛刀衩椒?你要知道,面試官真正想考察的是候選人是否具備某種能力和素質(zhì)哮兰,因?yàn)樵诂F(xiàn)代的前端開發(fā)中毛萌,處理異步的代碼隨處可見,熟悉和掌握異步操作的流程控制是成為合格開發(fā)者的基本功喝滞。

順著下來阁将,不難給出基于 Promise 的解決方案(既然 Promise 是 ES6 中的新特性,我們的新代碼使用 ES6 編寫是不是會(huì)更好右遭?如果你這么寫了做盅,大概率會(huì)讓面試官心生好感):

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吹榴,否則代碼不會(huì)按預(yù)期 work
            }, 1000 * j);   // 定時(shí)器的超時(shí)時(shí)間逐步增加
        }));
    })(i);
}

Promise.all(tasks).then(() => {
    setTimeout(() => {
        console.log(new Date, i);
    }, 1000);   // 注意這里只需要把超時(shí)設(shè)置為 1 秒
});

相比而言,筆者更傾向于下面這樣看起來更簡(jiǎn)潔的代碼滚婉,要知道編程風(fēng)格也是很多面試官重點(diǎn)考察的點(diǎn)图筹,代碼閱讀時(shí)的顆粒度更小,模塊化更好满哪,無疑會(huì)是加分點(diǎn)婿斥。

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

讀到這里的同學(xué),恭喜你民宿,你下次面試遇到類似的問題娇妓,至少能拿到 80 分。

我們都知道使用 Promise 處理異步代碼比回調(diào)機(jī)制讓代碼可讀性更高活鹰,但是使用 Promise 的問題也很明顯哈恰,即如果沒有處理 Promise 的 reject,會(huì)導(dǎo)致錯(cuò)誤被丟進(jìn)黑洞志群,好在新版的 Chrome 和 Node 7.x 能對(duì)未處理的異常給出 Unhandled Rejection Warning着绷,而排查這些錯(cuò)誤還需要一些特別的技巧(瀏覽器Node.js)锌云。

追問 3:ES7

既然你都看到這里了荠医,那就再堅(jiān)持 2 分鐘,接下來的內(nèi)容會(huì)讓你明白你的堅(jiān)持是值得的桑涎。

多數(shù)面試官在決定聘用某個(gè)候選人之前還需要考察另外一項(xiàng)重要能力彬向,即技術(shù)自驅(qū)力,直白的說就是候選人像有內(nèi)部的馬達(dá)在驅(qū)動(dòng)他攻冷,用漂亮的方式解決工程領(lǐng)域的問題娃胆,不斷的跟隨業(yè)務(wù)和技術(shù)變得越來越牛逼,究竟什么是牛逼等曼?建議閱讀程序人生的這篇剖析里烦。

回到正題,既然 Promise 已經(jīng)被拿下禁谦,如何使用 ES7 中的 async await 特性來讓這段代碼變的更簡(jiǎn)潔胁黑?你是否能夠根據(jù)自己目前掌握的知識(shí)給出答案?請(qǐng)?jiān)谶@里暫停 1 分鐘枷畏,思考下别厘。

下面是筆者給出的參考代碼:

// 模擬其他語言中的 sleep,實(shí)際上可以是任何異步操作
const sleep = (timeountMS) => new Promise((resolve) => {
    setTimeout(resolve, timeountMS);
});

(async () => {  // 聲明即執(zhí)行的 async 函數(shù)表達(dá)式
    for (var i = 0; i < 5; i++) {
        await sleep(1000);
        console.log(new Date, i);
    }

    await sleep(1000);
    console.log(new Date, i);
})();

總結(jié)

感謝你花時(shí)間讀到這里拥诡,相信你收獲的不僅僅是用 JS 精確控制代碼輸出的各種技巧,更是對(duì)于前端工程師的成長期許:扎實(shí)的語言基礎(chǔ)氮发、與時(shí)俱進(jìn)的能力渴肉、強(qiáng)大技術(shù)自驅(qū)力。

One More Thing

本文首發(fā)知乎專欄爽冕,商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)仇祭,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。如果你覺得本文對(duì)你有幫助颈畸,請(qǐng)點(diǎn)贊乌奇!如果對(duì)文中的內(nèi)容有任何疑問没讲,歡迎留言討論。想知道我接下來會(huì)寫些什么礁苗?歡迎訂閱知乎專欄:《前端周刊:讓你在前端領(lǐng)域跟上時(shí)代的腳步》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末爬凑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子试伙,更是在濱河造成了極大的恐慌嘁信,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疏叨,死亡現(xiàn)場(chǎng)離奇詭異潘靖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚤蔓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門卦溢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秀又,你說我怎么就攤上這事单寂。” “怎么了涮坐?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵凄贩,是天一觀的道長。 經(jīng)常有香客問我袱讹,道長疲扎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任捷雕,我火速辦了婚禮椒丧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘救巷。我一直安慰自己壶熏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布浦译。 她就那樣靜靜地躺著棒假,像睡著了一般。 火紅的嫁衣襯著肌膚如雪精盅。 梳的紋絲不亂的頭發(fā)上帽哑,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音叹俏,去河邊找鬼妻枕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的屡谐。 我是一名探鬼主播述么,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼愕掏!你這毒婦竟也來了度秘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤亭珍,失蹤者是張志新(化名)和其女友劉穎敷钾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肄梨,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阻荒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了众羡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侨赡。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖粱侣,靈堂內(nèi)的尸體忽然破棺而出羊壹,到底是詐尸還是另有隱情,我是刑警寧澤齐婴,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布油猫,位于F島的核電站,受9級(jí)特大地震影響柠偶,放射性物質(zhì)發(fā)生泄漏情妖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一诱担、第九天 我趴在偏房一處隱蔽的房頂上張望毡证。 院中可真熱鬧,春花似錦蔫仙、人聲如沸料睛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恤煞。三九已至,卻和暖如春施籍,著一層夾襖步出監(jiān)牢的瞬間阱州,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國打工法梯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓立哑,卻偏偏與公主長得像夜惭,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铛绰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容