JavaScript九大面試問題集錦,助你順利通關!
全文共6287字,預計學習時長20分鐘或更長
圖片來源:Irvan Smith / Unsplash
人們認為JavaScript是最適合初學者的語言。一部分原因在於JavaScript在網際網路中運用廣泛,另一部分原因在於其自身特性使得即使編寫的程式碼不那麼完美依然可以執行:無論是否少了一個分號或是記憶體管理問題,它都不像許多其他語言那樣嚴格,但在開始學習之前,要確保你已經知道JavaScript的來龍去脈,包括可以自動完成的事情和“幕後”的操作。
本文將介紹一些面試時關於JavaScript的常見問題,以及一些突發難題。當然,每次面試都是不同的,你也可能不會遇見這類問題。但是知道的越多,準備的就越充分。
第一部分:突發難題
如果在面試中突然問到下列問題,似乎很難回答。即便如此,這些問題在準備中仍發揮作用:它們揭示了JavaScript的一些有趣的功能,並強調在提出程式語言時,首先必須做出的一些決定。
瞭解有關JavaScript的更多功能,建議訪問https://wtfjs.com。
1. 為什麼Math.max()小於Math.min()?
Math.max()> Math.min()輸出錯誤這一說法看上去有問題,但其實相當合理。
如果沒有給出引數,Math.min()返回infinity(無窮大),Math.max()返回-infinity(無窮小)。這只是max()和min()方法規範的一部分,但選擇背後的邏輯值得深議。瞭解其中原因,請看以下程式碼:
Math.min(1) // 1 Math.min(1, infinity)// 1 Math.min(1, -infinity)// -infinity
如果-infinity(無窮小)作為Math.min()的預設引數,那麼每個結果都是-infinity(無窮小),這毫無用處! 然而,如果預設引數是infinity(無窮大),則無論新增任何引數返回都會是該數字 - 這就是我們想要的執行方式。
2. 為什麼0.1+0.2不等於0.3
簡而言之,這與JavaScript在二進位制中儲存浮點數的準確程度有關。在Google Chrome控制檯中輸入以下公式將得到:
0.1 + 0.2// 0.30000000000000004 0.1 + 0.2 - 0.2// 0.10000000000000003 0.1 + 0.7// 0.7999999999999999
如果是簡單的等式,對準確度沒有要求,這不太可能產生問題。但是如果需要測試相等性,即使是簡單地應用也會導致令人頭疼的問題。解決這些問題,有以下幾種方案。
Fixed Point固定點
例如,如果知道所需的最大精度(例如,如果正在處理貨幣),則可以使用整數型別來儲存該值。因此,可以儲存499而非4.99美元,並在此基礎上執行任何等式,然後可以使用類似result =(value / 100).toFixed(2)的表示式將結果顯示給終端使用者,該表示式返回一個字串。
BCD程式碼
如果精度非常重要,另一種方法是使用二進位制編碼的十進位制(BCD)格式,可以使用BCD庫(https://formats.kaitai.io/bcd/javascript.html)訪問JavaScript。每個十進位制值分別儲存在一個位元組(8位)中。鑑於一個位元組可以儲存16個單獨值,而該系統僅使用0-9位,所以這種方法效率低下。但是,如果十分注重精確度,採用何種方法都值得考量。
3. 為什麼018減017等於3?
018-017返回3實際是靜默型別轉換的結果。這種情況,討論的是八進位制數。
八進位制數簡介
你或許知道計算中使用二進位制(base-2)和十六進位制(base-16)數字系統,但是八進位制(base-8)在計算機歷史中的地位也舉足親重:在20世紀50年代後期和 20世紀60年代間,八進位制被用於簡化二進位制,削減高昂的製造系統中的材料成本。
不久以後Hexadecimal(十六進位制)開始登上歷史舞臺:
1965年釋出的IBM360邁出了從八進位制到十六進位制的決定性一步。我們這些習慣八進位制的人對這一舉措感到震驚!
沃恩·普拉特(Vaughan Pratt)
如今的八進位制數
但在現代程式語言中,八進位制又有何作用呢?針對某些案例,八進位制比十六進位制更具優勢,因為它不需要任何非數字(使用0-7而不是0-F)。
一個常見用途是Unix系統的檔案許可權,其中有八個許可權變體:
4 2 1
0 - - - no permissions
1 - - x only execute
2 - x - only write
3 - x x write and execute
4 x - - only read
5 x - x read and execute
6 x x - read and write
7 x x x read, write and execute
出於相似的原由,八進位制也用於數字顯示器。
回到問題本身
在JavaScript中,字首0將所有數字轉換為八進位制。但是,八進位制中不使用數字8,任何包含8的數字都將自動轉換為常規十進位制數。
因此,018-017實際上等同於十進位制表示式:18-15,因為017使用八進位制而018使用十進位制。
第二部分:常見問題
圖片來源:pexels.com/@divinetechygirl
本節中,將介紹面試中一些更加常見的JavaScript問題。第一次學習JavaScript時,這些問題容易被忽略。但在編寫最佳程式碼時,瞭解下述問題用處頗大。
4. 函式表示式與函式宣告有哪些不同?
函式宣告使用關鍵字function,後跟函式的名稱。相反,函式表示式以var,let或const開頭,後跟函式名稱和賦值運算子=。請看以下程式碼:
// Function Declaration function sum(x, y) { return x + y; }; // Function Expression: ES5 var sum = function(x, y) { return x + y;}; // Function Expression: ES6+ const sum = (x, y) => { return x + y };
實際操作中,關鍵的區別在於函式宣告要被提升,而函式表示式則沒有。這意味著JavaScript直譯器將函式宣告移動到其作用域的頂部,因此可以定義函式宣告並在程式碼中的任何位置呼叫它。相比之下,只能以線性順序呼叫函式表示式:必須在呼叫它之前解釋。
如今,許多開發人員偏愛函式表示式有如下幾個原因:
· 首先,函式表示式實施更加可預測的結構化程式碼庫。當然,函式宣告也可使用結構化程式碼庫; 只是函式宣告讓你更容易擺脫凌亂的程式碼。
· 其次,可以將ES6語法用於函式表示式:這通常更為簡潔,let和const可以更好地控制是否重新賦值變數,我們將在下一個問題中看到。
5. var,let和const有什麼區別?
自ES6釋出以來,現代語法已進入各行各業,這已是一個極其常見的面試問題。Var是第一版JavaScript中的變數宣告關鍵字。但它的缺點導致在ES6中採用了兩個新關鍵字:let和const。
這三個關鍵字具有不同的分配,提升和域 - 因此我們將單獨討論。
i) 分配
最基本的區別是let和var可以重新分配,而const則不能。這使得const成為不變變數的最佳選擇,並且它將防止諸如意外重新分配之類的失誤。注意,當變量表示陣列或物件時,const確實允許變數改變,只是無法重新分配變數本身。
Let 和var都可重新分配,但是正如以下幾點應該明確的那樣,如果不是所有情況都要求更改變數,多數選擇中,let具有優於var的顯著優勢。
ii)提升
與函式宣告和表示式(如上所述)之間的差異類似,使用var宣告的變數總是被提升到它們各自的頂部,而使用const和let宣告的變數被提升,但是如果你試圖在宣告之前訪問,將會得到一個TDZ(時間死區)錯誤。由於var可能更容易出錯,例如意外重新分配,因此運算是有用的。請看以下程式碼:
var x = "global scope"; function foo() { var x = "functional scope"; console.log(x); } foo(); // "functional scope" console.log(x); // "global scope"
這裡,foo()和console.log(x)的結果與預期一致。但是,如果去掉第二個變數又會發生什麼呢?
var x = "global scope"; function foo() { x = "functional scope"; console.log(x); }foo(); // "functional scope" console.log(x); // "functional scope"
儘管在函式內定義,但x =“functional scope”已覆蓋全域性變數。需要重複關鍵字var來指定第二個變數x僅限於foo()。
iii) 域
雖然var是function-scoped(函式作用域),但let和const是block-scoped(塊作用域的:一般情況下,Block是大括號{}內的任何程式碼,包括函式,條件語句和迴圈。為了闡明差異,請看以下程式碼:
var a = 0; let b = 0; const c = 0; if (true) { var a = 1; let b = 1; const c = 1; } console.log(a); // 1 console.log(b); // 0 console.log(c); // 0
在條件塊中,全域性範圍的var a已重新定義,但全域性範圍的let b和const c則沒有。一般而言,確保本地任務保持在本地執行,將使程式碼更加清晰,減少出錯。
6. 如果分配不帶關鍵字的變數會發生什麼?
如果不使用關鍵字定義變數,又會如何?從技術上講,如果x尚未定義,則x = 1是window.x = 1的簡寫。
要想完全杜絕這種簡寫,可以編寫嚴格模式,——在ES5中介紹過——在文件頂部或特定函式中寫use strict。後,當你嘗試宣告沒有關鍵字的變數時,你將收到一條報語法錯誤:Uncaught SyntaxError:Unexpected indentifier。
7. 面向物件程式設計(OOP)和函數語言程式設計(FP)之間的區別是什麼?
JavaScript是一種多正規化語言,即它支援多種不同的程式設計風格,包括事件驅動,函式和麵向物件。
程式設計正規化各有不同,但在當代計算中,函式程式設計和麵向物件程式設計最為流行 - 而JavaScript兩種都可執行。
面向物件程式設計
OOP以“物件”這一概念為基礎的資料結構,包含資料欄位(JavaScript稱為類)和程式(JavaScript中的方法)。
一些JavaScript的內建物件包括Math(用於random,max和sin等方法),JSON(用於解析JSON資料)和原始資料型別,如String,Array,Number和Boolean。
無論何時採用的內建方法,原型或類,本質上都在使用面向物件程式設計。
函式程式設計
FP(函式程式設計)以“純函式”的概念為基礎,避免共享狀態,可變資料和副作用。這可能看起來像很多術語,但可能已經在程式碼中建立了許多純函式。
輸入相同資料,純函式總是返回相同的輸出。這種方式沒有副作用:除了返回結果之外,例如登入控制檯或修改外部變數等都不會發生。
至於共享狀態,這裡有一個簡單的例子,即使輸入是相同的,狀態仍可以改變函式的輸出。設定一個具有兩個函式的程式碼:一個將數字加5,另一個將數字乘以5。
const num = { val: 1 };const add5 = () => num.val += 5; const multiply5 = () => num.val *= 5;
如果先呼叫add5在呼叫乘以5,則整體結果為30。但是如果以相反的方式執行函式並記錄結果,則輸出為10,與之前結果不一致。
這違背了函數語言程式設計的原理,因為函式的結果因Context呼叫方法而異。 重新編寫上面的程式碼,以便結果更易預測:
const num = { val: 1 }; const add5 = () => Object.assign({}, num, {val: num.val + 5}); const multiply5 = () => Object.assign({}, num, {val: num.val * 5});
現在,num.val的值仍然為1,無論Context呼叫的方法如何,add5(num)和multiply5(num)將始終輸出相同的結果。
8. 命令式和宣告性程式設計之間有什麼區別?
關於指令式程式設計和宣告式程式設計的區別,可以以OOP(面向物件程式設計)和FP(函數語言程式設計)為參考。
這兩種是描述多種不同程式設計正規化共有特徵的概括性術語。FP(函數語言程式設計)是宣告性程式設計的一個範例,而OOP(面向物件程式設計)是指令式程式設計的一個範例。
從基本的意義層面,指令式程式設計關注的是如何做某事。它以最基本的方式闡明瞭步驟,並以for和while迴圈,if和switch陳述句等為特徵。
const sumArray = array => { let result = 0; for (let i = 0; i < array.length; i++) { result += array[i] }; return result; }
相比之下,宣告性程式設計關注的是做什麼,它通過依賴表示式將怎樣做抽出來。這通常會產生更簡潔的程式碼,但是在規模上,由於透明度低,除錯會更加困難。
這是上述的sumArray()函式的宣告方法。
const sumArray = array => { return array.reduce((x, y) => x + y) };
圖片來源:pexels.com/@rawpixel
9. 是什麼基於原型的繼承?
最後,要講到的是基於原型的繼承。面向物件程式設計有幾種不同的型別,JavaScript使用的是基於原型的繼承。該系統通過使用現有物件作為原型,允許重複執行。
即使是首次遇到原型這一概念,使用內建方法時也會遇到原型系統。 例如,用於運算元組的函式(如map,reduce,splice等)都是Array.prototype物件的方法。實際上,陣列的每個例項(使用方括號[]定義,或者 -不常見的 new Array())都繼承自Array.prototype,這就是為什麼map,reduce和spliceare等方法都預設可用的原因。
幾乎所有內建物件都是如此,例如字串和布林運算:只有少數,如Infinity,NaN,null和undefined等沒有類或方法。
在原型鏈的末尾,能發現 Object.prototype,幾乎JavaScript中的每個物件都是Object的一個例項。比如Array. prototype和String. prototype都繼承了Object.prototype的類和方法。
要想對使用prototype syntax的物件新增類和方法,只需將物件作為函式啟動,並使用prototype關鍵字新增類和方法:
funcion Person() {}; Person.prototype.forename = "John"; Person.prototype.surname = "Smith";
是否應該覆蓋或擴充套件原型運算?
可以使用與建立擴充套件prototypes同樣的方式改變內建運算,但是大多數開發人員(以及大多數公司)不會建議這樣做。
如果希望多個物件進行同樣的運算,可以建立一個自定義物件(或定義你自己的“類”或“子類”),這些物件繼承內建原型而不改變原型本身。如果打算與其他開發人員合作,他們對JavaScript的預設行為有一定的預期,編輯此預設行為很容易導致出錯。
總的來說,這些問題能夠幫助你更好理解JavaScript,包括其核心功能和其他鮮為人知的功能 ,並且望能助你為下次的面試做好準備。
留言 點贊 關注
我們一起分享AI學習與發展的乾貨
歡迎關注全平臺AI垂類自媒體 “讀芯術”
(新增小編微信:dxsxbb,加入讀者圈,一起討論最新鮮的人工