每個(gè)JavaScript工程師都要知道的10個(gè)面試題
對大部分公司來說,招聘技術(shù)人員這種事情,管理層就應(yīng)該放手交給技術(shù)團(tuán)隊(duì),只有他們才能夠準(zhǔn)確地判斷應(yīng)聘者的技術(shù)實(shí)力。如果你恰巧是應(yīng)聘者,你也是遲早都要去面試的。不管你是哪邊的,都讓大哥來教你幾招。
以人為本
在 How to Build a High Velocity Development Team 一文中,我提出了一些觀點(diǎn),我覺得這些觀點(diǎn)很重要,所以在這里再重復(fù)一遍:
優(yōu)秀的團(tuán)隊(duì)才是決定公司業(yè)績的關(guān)鍵,一家公司要想于逆境之中仍能有所建樹,最重要的就是得先培養(yǎng)出一只優(yōu)秀的團(tuán)隊(duì)。
就像 Marcus Lemonis 說的,有三點(diǎn)(3 個(gè) P)最重要:
員工(People),流程(Process),產(chǎn)品(Product)。
在創(chuàng)業(yè)初期,你招來的工程師必須是能夠獨(dú)當(dāng)一面的大神隊(duì)友。他最好能夠幫著招聘工程師,能指導(dǎo)其它工程師,還能幫初級和中級工程師解決各種問題。這樣優(yōu)秀的隊(duì)友,無論何時(shí)都多多益善。
要想知道面試應(yīng)聘者時(shí),有哪些常見的注意事項(xiàng),可以讀讀 Why Hiring is So Hard in Tech 這篇文章。
要評估一個(gè)應(yīng)聘者的真實(shí)水準(zhǔn),最佳方式就是結(jié)對編程(pair programming)。
和應(yīng)聘者結(jié)對編程,一切都聽?wèi)?yīng)聘者的。多觀察、多聆聽,看看應(yīng)聘者是個(gè)怎樣的人。用微博的 API 抓取消息并顯示在時(shí)間線上,就是個(gè)很好的考察應(yīng)聘者的面試項(xiàng)目。
不過結(jié)對編程再好使,也沒辦法讓你完全了解一個(gè)應(yīng)聘者。這個(gè)時(shí)候,面試也能幫上很多忙——但是千萬別浪費(fèi)時(shí)間去問一些語法(syntax)或者語言上的細(xì)節(jié)(language quirks)——問些高端的問題吧,大兄弟。問問項(xiàng)目架構(gòu)(architecture),編程范式(paradigms),這個(gè)層面上的判斷(the big desicions)能夠在很大程度上影響一個(gè)項(xiàng)目的成敗。
語法和語言特性(features)這種小知識,Google 一搜一大把,誰都會。而工程師在工作中所積累的軟件工程方面的經(jīng)驗(yàn),以及個(gè)人常用的編程范式及代碼風(fēng)格(idioms),這些可都是很難 Google 到的寶貴財(cái)富。
JavaScript 很獨(dú)特,它在各種大型項(xiàng)目中都起著至關(guān)重要的作用。那是什么讓 JavaScript 如此與眾不同?
下面幾個(gè)問題,也許能幫你一探究竟。
1. 能說出來兩種對于 JavaScript 工程師很重要的編程范式么?
JavaScript 是一門多范式(multi-paradigm)的編程語言,它既支持命令式(imperative)/面向過程(procedural)編程,也支持面向?qū)ο缶幊蹋∣OP,Object-Oriented Programming),還支持函數(shù)式編程(functional programming)。JavaScript 所支持的面向?qū)ο缶幊贪?strong>原型繼承(prototypal inheritance)。
面試加分項(xiàng)
- 原型繼承(即:原型,OLOO——鏈接到其它對象的對象);
- 函數(shù)式編程(即:閉包(closure),一類函數(shù)(first class functions),lambda 函數(shù):箭頭函數(shù))。
面試減分項(xiàng)
- 連范式都不知道,更別提什么原型 OO(prototypal oo)或者函數(shù)式編程了。
深入了解
- The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
- The Two Pillars of JavaScript Part 2:JS 兩大支柱之二:函數(shù)式編程
2. 什么是函數(shù)式編程?
函數(shù)式編程,是將數(shù)學(xué)函數(shù)組合起來,并且避免了狀態(tài)共享(shared state)及可變數(shù)據(jù)(mutable data),由此而產(chǎn)生的編程語言。發(fā)明于 1958 年的 Lisp 就是首批支持函數(shù)式編程的語言之一,而 λ 演算(lambda calculus)則可以說是孕育了這門語言。即使在今天,Lisp 這個(gè)家族的編程語言應(yīng)用范圍依然很廣。
函數(shù)式編程可是 JavaScript 語言中非常重要的一個(gè)概念(它可是 JavaScript 的兩大支柱之一)。ES5 規(guī)范中就增加了很多常用的函數(shù)式工具。
面試加分項(xiàng)
- 純函數(shù)(pure functions)/函數(shù)的純粹性(function purity)
- 知道如何避免副作用(side-effects)
- 簡單函數(shù)的組合
- 函數(shù)式編程語言:Lisp,ML,Haskell,Erlang,Clojure,Elm,F(xiàn)#,OCaml,等等
- 提到了 JavaScript 語言中支持函數(shù)式編程(FP)的特性:一類函數(shù),高階函數(shù)(higher order functions),作為參數(shù)(arguments)/值(values)的函數(shù)
面試減分項(xiàng)
- 沒有提到純函數(shù),以及如何避免副作用
- 沒有提供函數(shù)式編程語言的例子
- 沒有說是 JavaScript 中的哪些特性使得函數(shù)式編程得以實(shí)現(xiàn)
深入了解
- The Two Pillars of JavaScript Part 2:JS 兩大支柱之二:函數(shù)式編程
- The Dao of Immutability
- Composing Software
- The Haskell School of Music
3. 類繼承和原型繼承有什么區(qū)別?
類繼承(Class Inheritance):實(shí)例(instances)由類繼承而來(類和實(shí)例的關(guān)系,可以類比為建筑圖紙和實(shí)際建筑 🏠 的關(guān)系),同時(shí)還會創(chuàng)建父類—子類這樣一種關(guān)系,也叫做類的分層分類(hierarchical class taxonomies)。通常是用 new
關(guān)鍵字調(diào)用類的構(gòu)造函數(shù)(constructor functions)來創(chuàng)建實(shí)例的。不過在 ES6 中,要繼承一個(gè)類,不用 class
關(guān)鍵字也可以。
原型繼承(Prototypal Inheritance):實(shí)例/對象直接從其它對象繼承而來,創(chuàng)建實(shí)例的話,往往用工廠函數(shù)(factory functions)或者 Object.create()
方法。實(shí)例可以從多個(gè)不同的對象組合而來,這樣就能選擇性地繼承了。
在 JavaScript 中,原型繼承比類繼承更簡單,也更靈活。
面試加分項(xiàng)
- 類:會創(chuàng)建緊密的耦合,或者說層級結(jié)構(gòu)(hierarchies)/分類(taxonomies)。
- 原型:提到了銜接繼承(concatenative inheritance)、原型委托( prototype delegation)、函數(shù)繼承(functional inheritance),以及對象組合(object composition)。
面試減分項(xiàng)
- 原型繼承和組合,與類繼承相比,不知道哪個(gè)更好。
深入了解
- The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
- Common Misconceptions About Inheritance in JavaScript:對于 JavaScript 中繼承這個(gè)概念,所普遍存在的誤解
4. 函數(shù)式編程和面向?qū)ο缶幊?,各有什么?yōu)點(diǎn)和不足呢?
面向?qū)ο缶幊痰膬?yōu)點(diǎn):關(guān)于“對象”的一些基礎(chǔ)概念理解起來比較容易,方法調(diào)用的含義也好解釋。面向?qū)ο缶幊掏ǔJ褂妹钍降木幋a風(fēng)格,聲明式(declarative style)的用得比較少。這樣的代碼讀起來,像是一組直接的、計(jì)算機(jī)很容易就能遵循的指令。
面向?qū)ο缶幊痰牟蛔?/strong>:面向?qū)ο缶幊掏枰蚕頎顟B(tài)。對象及其行為常常會添加到同一個(gè)實(shí)體上,這樣一來,如果一堆函數(shù)都要訪問這個(gè)實(shí)體,而且這些函數(shù)的執(zhí)行順序不確定的話,很可能就會出亂子了,比如競爭條件(race conditions)這種現(xiàn)象(函數(shù) A 依賴于實(shí)體的某個(gè)屬性,但是在 A 訪問屬性之前,屬性已經(jīng)被函數(shù) B 修改了,那么函數(shù) A 在使用屬性的時(shí)候,很可能就得不到預(yù)期的結(jié)果)。
函數(shù)式編程的優(yōu)點(diǎn):用函數(shù)式范式來編程,就不需要擔(dān)心共享狀態(tài)或者副作用了。這樣就避免了幾個(gè)函數(shù)在調(diào)用同一批資源時(shí)可能產(chǎn)生的 bug 了。擁有了“無參風(fēng)格”(point-free style,也叫隱式編程)之類的特性之后,函數(shù)式編程就大大簡化了,我們也可以用函數(shù)式編程的方式來把代碼組合成復(fù)用性更強(qiáng)的代碼了,面向?qū)ο缶幊炭勺霾坏竭@一點(diǎn)。
函數(shù)式編程更偏愛聲明式、符號式(denotational style)的編碼風(fēng)格,這樣的代碼,并不是那種為了實(shí)現(xiàn)某種目的而需要按部就班地執(zhí)行的一大堆指令,而是關(guān)注宏觀上要做什么。至于具體應(yīng)該怎么做,就都隱藏在函數(shù)內(nèi)部了。這樣一來,要是想重構(gòu)代碼、優(yōu)化性能,那就大有可為了。(譯者注:以做一道菜為例,就是由 買菜
-> 洗菜
-> 炒菜
這三步組成,每一步都是函數(shù)式編程的一個(gè)函數(shù),不管做什么菜,這個(gè)流程都是不會變的。而想要優(yōu)化這個(gè)過程,自然就是要深入每一步之中了。這樣不管內(nèi)部如何重構(gòu)、優(yōu)化,整體的流程并不會變,這就是函數(shù)式編程的好處。)甚至可以把一種算法換成另一種更高效的算法,同時(shí)還基本不需要修改代碼(比如把及早求值策略(eager evaluation)替換為惰性求值策略(lazy evaluation))。
利用純函數(shù)進(jìn)行的計(jì)算,可以很方便地?cái)U(kuò)展到多處理器環(huán)境下,或者應(yīng)用到分布式計(jì)算集群上,同時(shí)還不用擔(dān)心線程資源沖突、競爭條件之類的問題。
函數(shù)式編程的不足:代碼如果過度利用了函數(shù)式的編程特性(如無參風(fēng)格、大量方法的組合),就會影響其可讀性,從而簡潔度有余、易讀性不足。
大部分工程師還是更熟悉面向?qū)ο缶幊?、命令式編程,對于剛接觸函數(shù)式編程的人來說,即使只是這個(gè)領(lǐng)域的一些的簡單術(shù)語,都可能讓他懷疑人生。
函數(shù)式編程的學(xué)習(xí)曲線更陡峭,因?yàn)槊嫦驅(qū)ο缶幊烫占傲?,學(xué)習(xí)資料太多了。相比而言,函數(shù)式編程在學(xué)術(shù)領(lǐng)域的應(yīng)用更廣泛一些,在工業(yè)界的應(yīng)用稍遜一籌,自然也就不那么“平易近人”了。在探討函數(shù)式編程時(shí),人們往往用 λ 演算、代數(shù)、范疇學(xué)等學(xué)科的專業(yè)術(shù)語和專業(yè)符號來描述相關(guān)的概念,那么其他人想要入門函數(shù)式編程的話,就得先把這些領(lǐng)域的基礎(chǔ)知識搞明白,能不讓人頭大么。
面試加分項(xiàng)
- 共享狀態(tài)的缺點(diǎn)、資源競爭、等等(面向?qū)ο缶幊蹋?/li>
- 函數(shù)式編程能夠極大地簡化應(yīng)用開發(fā)
- 面向?qū)ο缶幊毯秃瘮?shù)式編程學(xué)習(xí)曲線的不同
- 兩種編程方式各自的不足之處,以及對代碼后期維護(hù)帶來的影響
- 函數(shù)式風(fēng)格的代碼庫,學(xué)習(xí)曲線會很陡峭
- 面向?qū)ο缶幊田L(fēng)格的代碼庫,修改起來很難,很容易出問題(和水平相當(dāng)?shù)暮瘮?shù)式風(fēng)格的代碼相比)
- 不可變性(immutability),能夠極大地提升程序歷史狀態(tài)(program state history)的可見性(accessible)和擴(kuò)展性(malleable),這樣一來,想要添加諸如無限撤銷/重做、倒帶/回放、可后退的調(diào)試之類的功能的話,就簡單多了。不管是面向?qū)ο缶幊踢€是函數(shù)式編程,這兩種范式都能實(shí)現(xiàn)不可變性,但是要用面向?qū)ο髞韺?shí)現(xiàn)的話,共享狀態(tài)對象的數(shù)量就會劇增,代碼也會變得復(fù)雜很多。
面試減分項(xiàng)
- 沒有講這兩種編程范式的缺點(diǎn)——如果熟悉至少其中一種范式的話,應(yīng)該能夠說出很多這種范式的缺點(diǎn)吧。
深入了解
總是你倆,看來你倆真是非常重要啊。
- The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
- The Two Pillars of JavaScript Part 2:JS 兩大支柱之二:函數(shù)式編程
5. 什么時(shí)候該用類繼承?
千萬別用類繼承!或者說盡量別用。如果非要用,就只用它繼承一級(one level)就好了,多級的類繼承簡直就是反模式的。這個(gè)話題(不太明白是關(guān)于什么的……)我也參與討論過好些年了,僅有的一些回答最終也淪為 常見的誤解 之一。更多的時(shí)候,這個(gè)話題討論著討論著就沒動(dòng)靜了。
如果一個(gè)特性有時(shí)候很有用
但有時(shí)候又很危險(xiǎn)
并且還有另一種更好的特性可以用
那務(wù)必要用另一種更好的特性
~ Douglas Crockford
面試加分項(xiàng)
- 盡量別用,甚至是徹底不用類繼承。
- 有時(shí)候只繼承一級的話也還是 OK 的,比如從框架的基類繼承,例如
React.Component
。 - 相比類繼承,對象組合(object composition)更好一些。
深入了解
- The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
- JS Objects — Inherited a Mess:JS 對象(繼承):只是繼承了混亂(mess)而已
6. 什么時(shí)候該用原型繼承?
原型繼承可以分為下面幾類:
- 委托(delegation,也就是原型鏈)
- 組合(concatenative,比如混用(mixins)、
Object.assign()
) - 函數(shù)式(functional,這個(gè)函數(shù)式原型繼承不是函數(shù)式編程。這里的函數(shù)是用來創(chuàng)建一個(gè)閉包,以實(shí)現(xiàn)私有狀態(tài)(private state)或者封裝(encapsulation))
上面這三種原型繼承都有各自的適用場景,不過它們都很有用,因?yàn)槎寄軐?shí)現(xiàn)組合繼承(composition),也就是建立了 A 擁有特性 B(has-a)、A 用到了特性 B(uses-a) 或者 A 可以實(shí)現(xiàn)特性 B(can-do) 的這樣一種關(guān)系。相比而言,類繼承建立的是 A 就是 B 這樣一種關(guān)系。
面試加分項(xiàng)
- 知道在什么情況下不適合用模塊化(modules)或者函數(shù)式編程。
- 知道需要組合多個(gè)不同來源的對象時(shí),應(yīng)該怎么做。
- 知道什么時(shí)候該用繼承。
面試減分項(xiàng)
- 不知道什么時(shí)候應(yīng)該用原型。
- 不知道混用和
Object.assign()
。
深入了解
- Programming JavaScript Applications:文章中的“原型”這一節(jié)
7. 為什么說“對象組合比類繼承更好”?
這句話引用的是《設(shè)計(jì)花紋》(Design Patterns,設(shè)計(jì)模式)這本書的內(nèi)容。意思是要想實(shí)現(xiàn)代碼重用,就應(yīng)該把一堆小的功能單元組合成滿足需求的各種對象,而不是通過類繼承弄出來一層一層的對象。
換句話說,就是盡量編程實(shí)現(xiàn) can-do、has-a 或者 uses-a 這種關(guān)系,而不是 is-a 這種關(guān)系。
面試加分項(xiàng)
- 避免使用類繼承。
- 避免使用問題多多的基類。
- 避免緊耦合。
- 避免極其不靈活的層次分類(taxonomy)(類繼承所產(chǎn)生的 is-a 關(guān)系可能會導(dǎo)致很多誤用的情況)
- 避免大猩猩香蕉問題(“你只是想要一根香蕉,結(jié)果最后卻整出來一只拿著香蕉的大猩猩,還有整個(gè)叢林”)。
- 要讓代碼更具擴(kuò)展性。
面試減分項(xiàng)
- 沒有提到上面任何一種問題。
- 沒有表達(dá)清楚對象組合與類繼承有什么區(qū)別,也沒有提到對象組合的優(yōu)點(diǎn)。
深入了解
8. 雙向數(shù)據(jù)綁定/單向數(shù)據(jù)流的含義和區(qū)別
雙向數(shù)據(jù)綁定(two-way data binding),意味著 UI 層所呈現(xiàn)的內(nèi)容和 Model 層的數(shù)據(jù)動(dòng)態(tài)地綁定在一起了,其中一個(gè)發(fā)生了變化,就會立刻反映在另一個(gè)上。比如用戶在前端頁面的表單控件中輸入了一個(gè)值,Model 層對應(yīng)該控件的變量就會立刻更新為用戶所輸入的值;反之亦然,如果 Modal 層的數(shù)據(jù)有變化,變化后的數(shù)據(jù)也會立刻反映至 UI 層。
單向數(shù)據(jù)流(one-way data flow), 意味著只有 Model 層才是單一數(shù)據(jù)源(single source of truth)。UI 層的變化會觸發(fā)對應(yīng)的消息機(jī)制,告知 Model 層用戶的目的(對應(yīng) React 的 store
)。只有 Model 層才有更改應(yīng)用狀態(tài)的權(quán)限,這樣一來,數(shù)據(jù)永遠(yuǎn)都是單向流動(dòng)的,也就更容易了解應(yīng)用的狀態(tài)是如何變化的。
采用單向數(shù)據(jù)流的應(yīng)用,其狀態(tài)的變化是很容易跟蹤的,采用雙向數(shù)據(jù)綁定的應(yīng)用,就很難跟蹤并理解狀態(tài)的變化了。
面試加分項(xiàng)
- React 是單向數(shù)據(jù)流的典型,面試時(shí)提到這個(gè)框架的話會加分。Cycle.js 則是另一個(gè)很流行的單向數(shù)據(jù)流的庫。
- Angular 則是雙向數(shù)據(jù)綁定的典型。
面試減分項(xiàng)
- 不理解單向數(shù)據(jù)流/雙向數(shù)據(jù)綁定的含義,也說不清楚兩者之間的區(qū)別。
深入了解
9. 單體架構(gòu)和微服務(wù)架構(gòu)各有何優(yōu)劣?
采用單體架構(gòu)(monolithic architecture)的應(yīng)用,各組件的代碼是作為一個(gè)整體存在的,組件之間互相合作,共享內(nèi)存和資源。
而微服務(wù)架構(gòu)(microservice architecture)則是由許許多多個(gè)互相獨(dú)立的小應(yīng)用組成,每個(gè)應(yīng)用都有自己的內(nèi)存空間,應(yīng)用在擴(kuò)容時(shí)也是獨(dú)立于其它應(yīng)用進(jìn)行的。
單體架構(gòu)的優(yōu)勢:大部分應(yīng)用都有相當(dāng)數(shù)量的橫切關(guān)注點(diǎn)(cross-cutting concerns),比如日志記錄,流量限制,還有審計(jì)跟蹤和 DOS 防護(hù)等安全方面的需求,單體架構(gòu)在這方面就很有優(yōu)勢。
當(dāng)所有功能都運(yùn)行在一個(gè)應(yīng)用里的時(shí)候,就可以很方便地將組件與橫切關(guān)注點(diǎn)相關(guān)聯(lián)。
單體架構(gòu)也有性能上的優(yōu)勢,畢竟訪問共享內(nèi)存還是比進(jìn)程間通信(inter-process communication,IPC)要快的。
單體架構(gòu)的劣勢:隨著單體架構(gòu)應(yīng)用功能的不斷開發(fā),各項(xiàng)服務(wù)之間的耦合程度也會不斷增加,這樣一來就很難把各項(xiàng)服務(wù)分離開來了,要做獨(dú)立擴(kuò)容或者代碼維護(hù)也就更不方便了。
微服務(wù)的優(yōu)勢:微服務(wù)架構(gòu)一般都有更好的組織結(jié)構(gòu),因?yàn)槊宽?xiàng)服務(wù)都有自己特定的分工,而且也不會干涉其它組件所負(fù)責(zé)的部分。服務(wù)解耦之后,想要重新組合、配置來為各個(gè)不同的應(yīng)用提供服務(wù)的話,也更方便了(比如同時(shí)為 Web 客戶端和公共 API 提供服務(wù))。
如果用合理的架構(gòu)來部署微服務(wù)的話,它在性能上也是很有優(yōu)勢的,因?yàn)檫@樣一來,就可以很輕松地分離熱門服務(wù),對其進(jìn)行擴(kuò)容,同時(shí)還不會影響到應(yīng)用中的其它部分。
微服務(wù)的劣勢:在實(shí)際構(gòu)建一個(gè)新的微服務(wù)架構(gòu)的時(shí)候,會遇到很多在設(shè)計(jì)階段沒有預(yù)料到的橫切關(guān)注點(diǎn)。如果是單體架構(gòu)應(yīng)用的話就很簡單,新建一個(gè)中間件(shared magic helpers 不知道怎么翻譯……)來解決這樣的問題就行了,沒什么麻煩的。
但是在微服務(wù)架構(gòu)中就不一樣了,要解決這個(gè)問題,要么為每個(gè)橫切關(guān)注點(diǎn)都引入一個(gè)獨(dú)立的模塊,要么就把所有橫切關(guān)注點(diǎn)的解決方案封裝到一個(gè)服務(wù)層中,讓所有流量都從這里走一遍就行了。
為了解決橫切關(guān)注點(diǎn)的問題,雖然單體架構(gòu)也趨向于把所有的路由流量都從一個(gè)外部服務(wù)層走一遍,但是在這種架構(gòu)中,可以等到項(xiàng)目非常成熟之后再進(jìn)行這種改造,這樣就可以把還這筆技術(shù)債的時(shí)間盡量往后拖一拖。
微服務(wù)一般都是部署在虛擬機(jī)或容器上的,隨著應(yīng)用規(guī)模的不斷增加,虛擬機(jī)搶工作(VM wrangling work)的情況也會迅速增加。任務(wù)的分配一般都是通過容器群(container fleet)管理工具來自動(dòng)實(shí)現(xiàn)的。
面試加分項(xiàng)
- 對于微服務(wù)的積極態(tài)度,雖然初始成本會比單體架構(gòu)要高一些。知道微服務(wù)的性能和擴(kuò)容在長期看來表現(xiàn)更佳。
- 在微服務(wù)架構(gòu)和單體架構(gòu)應(yīng)用上都有實(shí)戰(zhàn)經(jīng)驗(yàn)。能夠使應(yīng)用中的各項(xiàng)服務(wù)在代碼層面互相獨(dú)立,但是又可以在開發(fā)初期迅速地將各項(xiàng)服務(wù)打包成一整個(gè)的單體架構(gòu)應(yīng)用。微服務(wù)化的改造可以在應(yīng)用相當(dāng)成熟之后,改造成本在可承受范圍內(nèi)的時(shí)候再進(jìn)行。
面試減分項(xiàng)
- 不知道單體架構(gòu)和微服務(wù)架構(gòu)的區(qū)別。
- 不知道微服務(wù)架構(gòu)額外的開銷,或者沒有實(shí)際經(jīng)驗(yàn)。
- 不知道微服務(wù)架構(gòu)中,IPC 和網(wǎng)絡(luò)通信所導(dǎo)致的額外的性能開銷。
- 過分貶低微服務(wù)。說不清楚什么時(shí)候應(yīng)該把單體架構(gòu)應(yīng)用解耦成微服務(wù)。
- 低估了可獨(dú)立擴(kuò)容的微服務(wù)的優(yōu)勢。
10. 異步編程是什么?又為什么在 JavaScript 中這么重要?
在同步編程中,代碼會按順序自頂向下依次執(zhí)行(條件語句和函數(shù)調(diào)用除外),如果遇到網(wǎng)絡(luò)請求或者磁盤讀/寫(I/O)這類耗時(shí)的任務(wù),就會堵塞在這樣的地方。
在異步編程中,JS 運(yùn)行在事件循環(huán)(event loop)中。當(dāng)需要執(zhí)行一個(gè)阻塞操作(blocking operation)時(shí),主線程發(fā)起一個(gè)(異步)請求,(工作線程就會去執(zhí)行這個(gè)異步操作,)同時(shí)主線程繼續(xù)執(zhí)行后面的代碼。(工作線程執(zhí)行完畢之后,)就會發(fā)起響應(yīng),觸發(fā)中斷(interrupt),執(zhí)行事件處理程序(event handler),執(zhí)行完后主線程繼續(xù)往后走。這樣一來,一個(gè)程序線程就可以處理大量的并發(fā)操作了。
用戶界面(user interface,UI)天然就是異步的,大部分時(shí)間它都在等待用戶輸入,從而中斷事件循環(huán),觸發(fā)事件處理程序。
Node.js 默認(rèn)是異步的,采用它構(gòu)建的服務(wù)端和用戶界面的執(zhí)行機(jī)制差不多,在事件循環(huán)中等待網(wǎng)絡(luò)請求,然后一個(gè)接一個(gè)地處理這些請求。
異步在 JavaScript 中非常重要,因?yàn)樗冗m合編寫 UI,在服務(wù)端也有上佳的性能表現(xiàn)。
面試加分項(xiàng)
- 理解阻塞的含義,以及對性能帶來的影響。
- 理解事件處理程序,以及它為什么對 UI 部分的代碼很重要。
面試減分項(xiàng)
- 不熟悉同步、異步的概念。
- 講不清楚異步代碼和 UI 代碼的性能影響,也說不明白它倆之間的關(guān)系。
總結(jié)
多問問應(yīng)聘者高層次的知識點(diǎn),如果能講清楚這些概念,就說明即使應(yīng)聘者沒怎么接觸過 JavaScript,也能夠在短短幾個(gè)星期之內(nèi)就把語言細(xì)節(jié)和語法之類的東西弄清楚。
不要因?yàn)閼?yīng)聘者在一些簡單的知識上表現(xiàn)不佳就把對方 pass 掉,比如經(jīng)典的 CS-101 算法課,或者一些解謎類的題目。
面試官真正應(yīng)該關(guān)注的,是應(yīng)聘者是否知道如何把一堆功能組織在一起,形成一個(gè)完整的應(yīng)用。
電話面試的注意點(diǎn)就這些了,在線下的面試中,我更加關(guān)注應(yīng)聘者實(shí)際編寫代碼的能力,我會觀察他如何寫代碼。在我的《精通 JavaScript 面試》這個(gè)系列文章中,會有更深入的描述。