1. 能說出來兩種關於 JavaScript 工程師很主要的編程範式么?
JavaScript 是一門多範式(multi-paradigm)的編程言語,它既支撐敕令式(imperative)/面向歷程(procedural)編程,也支撐面向對象編程(OOP,Object-Oriented Programming),還支撐函數式編程(functional programming)。JavaScript 所支撐的面向對象編程包含原型繼承(prototypal inheritance)。
口試加分項
原型繼承(即:原型,OLOO——鏈接到別的對象的對象);
函數式編程(即:閉包(closure),一類函數(first class functions),lambda 函數:箭頭函數)。
口試減分項
連範式都不曉得,更別提什麼原型 OO(prototypal oo)或許函數式編程了。
深切相識
The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
The Two Pillars of JavaScript Part 2:JS 兩大支柱之二:函數式編程
2. 什麼是函數式編程?
函數式編程,是將數學函數組合起來,而且防止了狀況同享(shared state)及可變數據(mutable data),由此而發生的編程言語。發明於 1958 年的 Lisp 就是首批支撐函數式編程的言語之一,而 λ 演算(lambda calculus)則可以說是孕育了這門言語。縱然在本日,Lisp 這個家屬的編程言語運用局限依舊很廣。
函數式編程然則 JavaScript 言語中異常主要的一個觀點(它然則 JavaScript 的兩大支柱之一)。ES5 範例中就增添了許多經常運用的函數式東西。
口試加分項
純函數(pure functions)/函數的地道性(function purity)
曉得怎樣防止副作用(side-effects)
簡樸函數的組合
函數式編程言語:Lisp,ML,Haskell,Erlang,Clojure,Elm,F#,OCaml,等等
提到了 JavaScript 言語中支撐函數式編程(FP)的特徵:一類函數,高階函數(higher order functions),作為參數(arguments)/值(values)的函數
口試減分項
沒有提到純函數,以及怎樣防止副作用
沒有供應函數式編程言語的例子
沒有說是 JavaScript 中的哪些特徵使得函數式編程得以完成
深切相識
The Two Pillars of JavaScript Part 2:JS 兩大支柱之二:函數式編程
The Dao of Immutability
Composing Software
The Haskell School of Music
3. 類繼承和原型繼承有什麼區分?
類繼承(Class Inheritance):實例(instances)由類繼承而來(類和實例的關聯,可以類比為修建圖紙和現實修建 🏠 的關聯),同時還會豎立父類—子類如許一種關聯,也叫做類的分層分類(hierarchical class taxonomies)。平常是用 new 關鍵字挪用類的構造函數(constructor functions)來豎立實例的。不過在 ES6 中,要繼承一個類,不必 class 關鍵字也可以。
原型繼承(Prototypal Inheritance):實例/對象直接從別的對象繼承而來,豎立實例的話,每每用工場函數(factory functions)或許 Object.create() 要領。實例可以從多個差別的對象組合而來,如許就可以挑選性地繼承了。
在 JavaScript 中,原型繼承比類繼承更簡樸,也更天真。
口試加分項
類:會豎立嚴密的耦合,或許說層級構造(hierarchies)/分類(taxonomies)。
原型:提到了連接繼承(concatenative inheritance)、原型託付( prototype delegation)、函數繼承(functional inheritance),以及對象組合(object composition)。
口試減分項
原型繼承和組合,與類繼承比擬,不曉得哪一個更好。
深切相識
The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
Common Misconceptions About Inheritance in JavaScript:關於 JavaScript 中繼承這個觀點,所普遍存在的誤會
4. 函數式編程和面向對象編程,各有什麼長處和不足呢?
面向對象編程的長處:關於“對象”的一些基礎觀點邃曉起來比較輕易,要領挪用的寄義也好詮釋。面向對象編程平常運用敕令式的編碼作風,聲明式(declarative style)的用得比較少。如許的代碼讀起來,像是一組直接的、盤算機很輕易就可以遵照的指令。
面向對象編程的不足:面向對象編程每每須要同享狀況。對象及其行動常常會增添到統一個實體上,如許一來,假如一堆函數都要接見這個實體,而且這些函數的實行遞次不確定的話,極可以就會出亂子了,比方合作前提(race conditions)這類徵象(函數 A 依賴於實體的某個屬性,然則在 A 接見屬性之前,屬性已被函數 B 修正了,那末函數 A 在運用屬性的時刻,極可以就得不到預期的效果)。
函數式編程的長處:用函數式範式來編程,就不須要憂鬱同享狀況或許副作用了。如許就防止了幾個函數在挪用統一批資本時可以發生的 bug 了。具有了“無參作風”(point-free style,也叫隱式編程)之類的特徵以後,函數式編程就大大簡化了,我們也可以用函數式編程的體式格局來把代碼組合成復用性更強的代碼了,面向對象編程可做不到這一點。
函數式編程更偏幸聲明式、標記式(denotational style)的編碼作風,如許的代碼,並非那種為了完成某種目標而須要循序漸進地實行的一大堆指令,而是關注宏觀上要做什麼。至於詳細應當怎樣做,就都隱藏在函數內部了。如許一來,如果想重構代碼、優化機能,那就大有可為了。(譯者注:以做一道菜為例,就是由 買菜 -> 洗菜 -> 炒菜 這三步構成,每一步都是函數式編程的一個函數,不論做什麼菜,這個流程都是不會變的。而想要優化這個歷程,自然就是要深切每一步之中了。如許不論內部怎樣重構、優化,團體的流程並不會變,這就是函數式編程的優點。)以至可以把一種算法換成另一種更高效的算法,同時還基礎不須要修正代碼(比方把儘早求值戰略(eager evaluation)替換為惰性求值戰略(lazy evaluation))。
應用純函數舉行的盤算,可以很方便地擴展到多處置懲罰器環境下,或許運用到分佈式盤算集群上,同時還不必憂鬱線程資本爭執、合作前提之類的問題。
函數式編程的不足:代碼假如太過應用了函數式的編程特徵(如無參作風、大批要領的組合),就會影響其可讀性,從而簡約度有餘、易讀性不足。
大部份工程師照樣更熟習面向對象編程、敕令式編程,關於剛打仗函數式編程的人來講,縱然只是這個領域的一些的簡樸術語,都可以讓他疑心人生。
函數式編程的進修曲線更峻峭,由於面向對象編程太提高了,進修材料太多了。比擬而言,函數式編程在學術領域的運用更普遍一些,在工業界的運用稍遜一籌,自然也就不那末“平易近民”了。在議論函數式編程時,人們每每用 λ 演算、代數、領域學等學科的專業術語和專業標記來形貌相干的觀點,那末其他人想要入門函數式編程的話,就得先把這些領域的基礎學問搞邃曉,能不讓人頭大么。
口試加分項
同享狀況的瑕玷、資本合作、等等(面向對象編程)
函數式編程可以極大地簡化運用開闢
面向對象編程和函數式編程進修曲線的差別
兩種編程體式格局各自的不足之處,以及對代碼後期保護帶來的影響
函數式作風的代碼庫,進修曲線會很峻峭
面向對象編程作風的代碼庫,修正起來很難,很輕易出問題(和水平相稱的函數式作風的代碼比擬)
不可變性(immutability),可以極大地提拔順序汗青狀況(program state history)的可見性(accessible)和擴展性(malleable),如許一來,想要增添諸如無窮打消/重做、倒帶/回放、可退卻的調試之類的功用的話,就簡樸多了。不論是面向對象編程照樣函數式編程,這兩種範式都能完成不可變性,然則要用面向對象來完成的話,同享狀況對象的數目就會劇增,代碼也會變得複雜許多。
口試減分項
沒有講這兩種編程範式的瑕玷——假如熟習最少个中一種範式的話,應當可以說出許多這類範式的瑕玷吧。
深切相識
老是你倆,看來你倆真是異常主要啊。
The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
The Two Pillars of JavaScript Part 2:JS 兩大支柱之二:函數式編程
5. 什麼時刻該用類繼承?
萬萬別用類繼承!或許說只管別用。假如非要用,就只用它繼承一級(one level)就好了,多級的類繼承幾乎就是反形式的。這個話題(不太邃曉是關於什麼的……)我也介入議論過好些年了,唯一的一些回覆終究也淪為 罕見的誤會 之一。更多的時刻,這個話題議論着議論着就沒動靜了。
假如一個特徵有時刻很有效 但有時刻又很風險 而且另有另一種更好的特徵可以用 那務必要用另一種更好的特徵~ Douglas Crockford
口試加分項
只管別用,以至是完全不必類繼承。
有時刻只繼承一級的話也照樣 OK 的,比方從框架的基類繼承,比方 React.Component。
比擬類繼承,對象組合(object composition)更好一些。
深切相識
The Two Pillars of JavaScript Part 1:JS 兩大支柱之一:原型 OO
JS Objects — Inherited a Mess:JS 對象(繼承)——只是繼承了雜沓(mess)罷了
6. 什麼時刻該用原型繼承?
原型繼承可以分為下面幾類:
託付(delegation,也就是原型鏈)
組合(concatenative,比方混用(mixins)、 Object.assign())
函數式(functional,這個函數式原型繼承不是函數式編程。這裏的函數是用來豎立一個閉包,以完成私有狀況(private state)或許封裝(encapsulation))
上面這三種原型繼承都有各自的實用場景,不過它們都很有效,由於都能完成組合繼承(composition),也就是豎立了 A 具有特徵 B(has-a)、A 用到了特徵 B(uses-a) 或許 A 可以完成特徵 B(can-do) 的如許一種關聯。比擬而言,類繼承豎立的是 A 就是 B 如許一種關聯。
口試加分項
曉得在什麼狀況下不合適用模塊化(modules)或許函數式編程。
曉得須要組合多個差別泉源的對象時,應當怎樣做。
曉得什麼時刻該用繼承。
口試減分項
不曉得什麼時刻應當用原型。
不曉得混用和 Object.assign()。
深切相識
Programming JavaScript Applications:文章中的“原型”這一節
7. 為何說“對象組合比類繼承更好”?
這句話援用的是《設想斑紋》(Design Patterns,設想形式)這本書的內容。意義是要想完成代碼重用,就應當把一堆小的功用單位組合成滿足需求的種種對象,而不是經由過程類繼承弄出來一層一層的對象。
換句話說,就是只管編程完成 can-do、has-a 或許 uses-a 這類關聯,而不是 is-a 這類關聯。
口試加分項
防止運用類繼承。
防止運用問題多多的基類。
防止緊耦合。
防止極為不天真的條理分類(taxonomy)(類繼承所發生的 is-a 關聯可以會致使許多誤用的狀況)
防止大猩猩香蕉問題(“你只是想要一根香蕉,效果末了卻整出來一隻拿着香蕉的大猩猩,另有全部森林”)。
要讓代碼更具擴展性。
口試減分項
沒有提到上面任何一種問題。
沒有表達清晰對象組合與類繼承有什麼區分,也沒有提到對象組合的長處。
深切相識
Composition over Inheritance
Introducing the Stamp Specification
8. 雙向數據綁定/單向數據流的寄義和區分
雙向數據綁定(two-way data binding),意味着 UI 層所顯現的內容和 Model 層的數據動態地綁定在一起了,个中一個發生了變化,就會馬上反應在另一個上。比方用戶在前端頁面的表單控件中輸入了一個值,Model 層對應當控件的變量就會馬上更新為用戶所輸入的值;反之亦然,假如 Modal 層的數據有變化,變化后的數據也會馬上反應至 UI 層。
單向數據流(one-way data flow), 意味着只要 Model 層才是單一數據源(single source of truth)。UI 層的變化會觸發對應的音訊機制,示知 Model 層用戶的目標(對應 React 的 store)。只要 Model 層才有變動運用狀況的權限,如許一來,數據永遠都是單向活動的,也就更輕易相識運用的狀況是怎樣變化的。
採納單向數據流的運用,其狀況的變化是很輕易跟蹤的,採納雙向數據綁定的運用,就很難跟蹤並邃曉狀況的變化了。
口試加分項
React 是單向數據流的典範,口試時提到這個框架的話會加分。Cycle.js 則是另一個很盛行的單向數據流的庫。
Angular 則是雙向數據綁定的典範。
口試減分項
不邃曉單向數據流/雙向數據綁定的寄義,也說不清晰兩者之間的區分。
深切相識
Introduction to React.js
9. 單體架構和微效勞架構各有何好壞?
採納單體架構(monolithic architecture)的運用,各組件的代碼是作為一個團體存在的,組件之間相互合作,同享內存和資本。
而微效勞架構(microservice architecture)則是由許許多多個相互自力的小運用構成,每一個運用都有本身的內存空間,運用在擴容時也是自力於別的運用舉行的。
單體架構的上風:大部份運用都有相稱數目的橫切關注點(cross-cutting concerns),比方日記紀錄,流量限定,另有審計跟蹤和 DOS 防護等平安方面的需求,單體架構在這方面就很有上風。
當一切功用都運行在一個運用里的時刻,就可以夠很方便地將組件與橫切關注點相干聯。
單體架構也有機能上的上風,畢竟接見同享內存照樣比歷程間通訊(inter-process communication,IPC)要快的。
單體架構的劣勢:跟着單體架構運用功用的不停開闢,各項效勞之間的耦合水平也會不停增添,如許一來就很難把各項效勞星散開來了,要做自力擴容或許代碼保護也就更不方便了。
微效勞的上風:微效勞架構平常都有更好的構造構造,由於每項效勞都有本身特定的分工,而且也不會過問別的組件所擔任的部份。效勞解耦以後,想要重新組合、設置來為各個差別的運用供應效勞的話,也更方便了(比方同時為 Web 客戶端和大眾 API 供應效勞)。
假如用合理的架構來布置微效勞的話,它在機能上也是很有上風的,由於如許一來,就可以夠很輕鬆地星散熱點效勞,對其舉行擴容,同時還不會影響到運用中的別的部份。
微效勞的劣勢:在現實構建一個新的微效勞架構的時刻,會碰到許多在設想階段沒有預料到的橫切關注點。假如是單體架構運用的話就很簡樸,新建一个中間件(shared magic helpers 不曉得怎樣翻譯……)來處置懲罰如許的問題就好了,沒什麼貧苦的。
然則在微效勞架構中就不一樣了,要處置懲罰這個問題,要麼為每一個橫切關注點都引入一個自力的模塊,要麼就把一切橫切關注點的處置懲罰方案封裝到一個效勞層中,讓一切流量都從這裏走一遍就好了。
為相識決橫切關注點的問題,雖然單體架構也趨向於把一切的路由流量都從一個外部效勞層走一遍,然則在這類架構中,可以比及項目異常成熟以後再舉行這類革新,如許就可以夠把還這筆手藝債的時候只管今後拖一拖。
微效勞平常都是布置在虛擬機或容器上的,跟着運用範圍的不停增添,虛擬機搶事情(VM wrangling work)的狀況也會敏捷增添。使命的分派平常都是經由過程容器群(container fleet)管理東西來自動完成的。
口試加分項
關於微效勞的积極態度,雖然初始本錢會比單體架構要高一些。曉得微效勞的機能和擴容在歷久看來表現更佳。
在微效勞架構和單體架構運用上都有實戰履歷。可以使運用中的各項效勞在代碼層面相互自力,然則又可以在開闢早期敏捷地將各項效勞打包成一全部的單體架構運用。微效勞化的革新可以在運用相稱成熟以後,革新本錢在可蒙受局限內的時刻再舉行。
口試減分項
不曉得單體架構和微效勞架構的區分。
不曉得微效勞架構分外的開支,或許沒有現實履歷。
不曉得微效勞架構中,IPC 和收集通訊所致使的分外的機能開支。
太過誹謗微效勞。說不清晰什麼時刻應當把單體架構運用解耦成微效勞。
低估了可自力擴容的微效勞的上風。
10. 異步編程是什麼?又為何在 JavaScript 中這麼主要?
在同步編程中,代碼會按遞次自頂向下順次實行(前提語句和函數挪用除外),假如碰到收集要求或許磁盤讀/寫(I/O)這類耗時的使命,就會梗塞在如許的處所。
在異步編程中,JS 運行在事宜輪迴(event loop)中。當須要實行一個壅塞操縱(blocking operation)時,主線程提議一個(異步)要求,(事情線程就會去實行這個異步操縱,)同時主線程繼承實行背面的代碼。(事情線程實行終了以後,)就會提議相應,觸發中綴(interrupt),實行事宜處置懲罰順序(event handler),實行完後主線程繼承今後走。如許一來,一個順序線程就可以夠處置懲罰大批的併發操縱了。
用戶界面(user interface,UI)自然就是異步的,大部份時候它都在守候用戶輸入,從而中綴事宜輪迴,觸發事宜處置懲罰順序。
Node.js 默許是異步的,採納它構建的效勞端和用戶界面的實行機制差不多,在事宜輪迴中守候收集要求,然後一個接一個地處置懲罰這些要求。
異步在 JavaScript 中異常主要,由於它既合適編寫 UI,在效勞端也有上佳的機能表現。
口試加分項
邃曉壅塞的寄義,以及對機能帶來的影響。
邃曉事宜處置懲罰順序,以及它為何對 UI 部份的代碼很主要。
口試減分項
不熟習同步、異步的觀點。
講不清晰異步代碼和 UI 代碼的機能影響,也說不邃曉它倆之間的關聯。
總結
多問問應聘者高條理的學問點,假如能講清晰這些觀點,就申明縱然應聘者沒怎樣打仗過 JavaScript,也可以在短短几個禮拜以內就把言語細節和語法之類的東西弄清晰。
不要由於應聘者在一些簡樸的學問上表現不佳就把對方 pass 掉,比方典範的 CS-101 算法課,或許一些解謎類的問題。
口試官真正應當關注的,是應聘者是不是曉得怎樣把一堆功用構造在一起,構成一個完全的運用。
電話口試的注重點就這些了,在線下的口試中,我越發關注應聘者現實編寫代碼的才能,我會視察他怎樣寫代碼。在我的《通曉 JavaScript 口試》這個系列文章中,會有更深切的形貌。