26個精選的 JavaScript 面試問題
為了保證可讀性,本文采用意譯而非直譯。另外,本文版權歸原作者所有,翻譯僅用於學習
根據Stack Overflow 2018年年度調查報告,JavaScript已經連續6年保持最常用的程式語言的記錄。對於一個全棧工程師,JavaScript可以說是一項必備語言,在面試中總會被問到。我整理了一下FullStack.Cafe上所有常見的JavaScript面試問題供大家參考:
Q1: JavaScript中型別轉換是怎樣的?
話題: JavaScript 難度: 0
在JavaScript中,在兩個不同型別之間的轉換叫做coercion
。在JavaScript中有兩種形式:顯示轉換和隱式轉換。
var a = "42";
var b = Number( a );
a; // "42"
b; // 42 -- the number!
下面是一個隱式轉換的例子:
var a = "42"; var b = a * 1; // "42" implicitly coerced to 42 here a; // "42" b; // 42 -- the number!
來源: FullStack.Cafe
Q2: JavaScript中的作用域是怎樣的?
話題: JavaScript 難度: ⭐
在JavaScript中,每一個函式都有各自的作用域(scope
)。作用域可以理解為是一個變數的集合以及相應的如何訪問它的規則。只有在函式內部的變數才可以訪問到該函式域的變數。
在同一個作用域內部,變數名必須要唯一。作用域可以巢狀。在最內部的作用域中,可以訪問任何外部的作用域中的變數。
Q3: 請解釋JavaScript中的相等判斷
話題: JavaScript 難度: ⭐
JavaScript中的相等判斷有嚴格判斷和帶隱式轉換的判斷兩種:
- 嚴格判斷(strict comparision): 比如
===
,比較的時候不會隱式轉換型別; - 抽象判斷(abstract comparasion):比如
==
,比較的時候會隱式轉換型別。
var a = "42";
var b = 42;
a == b; // true
a === b; // false
一些簡單的規則:
- 如果兩邊都是布林型別的值,使用
===
; - 如果兩邊是0,
""
,[]
,使用===
; - 所有其它型別,使用
==
是安全的。而且在很多情況下會簡化程式碼、增加可讀性。
Q4: 請解釋什麼叫做回撥函式並提供一個簡單的例子
話題: JavaScript 難度: ⭐⭐
回撥函式是一個函式,它被作為引數傳入另一個函式,當某些操作結束後,該函式被呼叫。下面是一個簡單的例子,當陣列被修改後,呼叫回撥函式列印一行日誌。
function modifyArray(arr, callback) {
// do something to arr here
arr.push(100);
// then execute the callback function that was passed
callback();
}
var arr = [1, 2, 3, 4, 5];
modifyArray(arr, function() {
console.log("array has been modified", arr);
});
Q5: "use strict"到底有何用處?
話題: JavaScript 難度: ⭐⭐
use strict
放在檔案的頂部或則函式的第一行來啟動更加嚴格的檢查來避免失誤引起的錯誤。比如,下面的程式碼會丟擲錯誤:
function doSomething(val) {
"use strict";
x = val + 10;
}
因為x沒有定義,如果使用了use strict
,x是不會被當做全域性的變數來看待。下面的程式碼修復了這個BUG:
function doSomething(val) {
"use strict";
var x = val + 10;
}
Q6: 請解釋Null和Undefined
話題: JavaScript 難度: ⭐⭐
JavaScript和TypeScript有兩個最基本的型別null
和undefined
。它們的含義是不同的:
- 如果還沒有被初始化,則是
undefined
; - 如果不可用,則可以用
null
來表示;
Q7: 請實現如下函式
話題: JavaScript 難度: ⭐⭐
var addSix = createBase(6);
addSix(10); // returns 16
addSix(21); // returns 27
addSix
是一個函式,也就是說createBase函式的返回是一個函式。
function createBase(baseNumber) {
return function(N) {
// we are referencing baseNumber here even though it was declared
// outside of this function. Closures allow us to do this in JavaScript
return baseNumber + N;
}
}
var addSix = createBase(6);
addSix(10);
addSix(21);
Q8: 請解釋JavaScript中的值和型別
話題: JavaScript 難度: ⭐⭐
下面是JavaScript內建的可用型別:
- string
- number
- boolean
- null和undefined
- object
- symbol (ES6的新語法)
Q9: 請解釋事件冒泡以及如何阻止它?
話題: JavaScript 難度: ⭐⭐
事件冒泡的概念是指:在最內層的元素上繫結的事件被觸發後,會按照巢狀的層次由內向外逐步觸發。因此,點選某個孩子節點可能會觸發父節點的事件。
一個阻止事件冒泡的辦法就是使用event.stopPropagation()
,在IE<9的瀏覽器上使用event.cancelBubble
。
function stopPropagation(evt) {
if (typeof evt.stopPropagation === "function") {
evt.stopPropagation();
} else {
evt.cancelBubble = true;
}
}
Q10. 請解釋JavaScript中的let關鍵字
話題: JavaScript 難度: ⭐⭐
ES6允許你使用let關鍵字來申明塊作用域({...}
)的變數。
Q11: 如何檢查一個數字是否是整數?
話題: JavaScript 難度: ⭐⭐
一個最簡單的方法是判斷除以1的餘數是否為0.
function isInt(num) {
return num % 1 === 0;
}
console.log(isInt(4)); // true
console.log(isInt(12.2)); // false
console.log(isInt(0.3)); // false
來源: coderbyte.com
Q12: 什麼叫IIFEs(Immediately Invoked Function Expressions)?
話題: JavaScript 難度: ⭐⭐
IIFE叫做立即執行表示式,顧名思義,該表示式一被建立就立即執行。
(function IIFE(){
console.log( "Hello!" );
})();
// "Hello!"
該方法常用語避免汙染全域性的名稱空間,因為所以在IIFE中使用的變數外部都無法訪問。
Q13: 如果比較JavaScript中的兩個物件?
話題: JavaScript 難度: ⭐⭐
兩個非基本型別的值,比如物件(包括函式和陣列)都是通過引用的形式來訪問。如果直接通過==
和===
來判斷,那麼只會簡單的判斷其引用地址是否相同,而不是它們實際對應的值。
如果陣列和字串做比較,那麼陣列會通過逗號拼接轉換為字串。通過等號判斷的時候,兩個相同的陣列不會相等,但是和相同資料的字串比較反而相等。
var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";
a == c; // true
b == c; // true
a == b; // false
如果要深度比較,可以使用第三方庫,比如deep-equal
或則你自己實現一個比較演算法。
Q14: 請解釋ES5和ES6的不同點
話題: JavaScript 難度: ⭐⭐⭐
- ECMAScript 5 (ES5): 第5個ECMAScript版本,於2009年標準化。該標準幾乎所有的瀏覽器都完全支援。
- ECMAScript 6 (ES6)/ECMAScript 2015 (ES2015): 第6個ECMAScript版本,於2015年標準化。目前各大瀏覽器還只是部分支援。
接下來介紹它們主要的區別:
- 箭頭函式和字串嵌入:
const greetings = (name) => {
return `hello ${name}`;
}
甚至:
const greetings = name => `hello ${name}`;
- 常量宣告(Const): 如同其它程式語言中的常量一樣,但又有不同。這裡的
const
代表了constant reference
。也就是說,你可以修改其指向的物件的值。但是你不能修改其reference的值。
const NAMES = [];
NAMES.push("Jim");
console.log(NAMES.length === 1); // true
NAMES = ["Steve", "John"]; // error
- 塊作用域變數:ES6中的新關鍵字
let
允許允許開發者將變數的作用域限定在塊級別。不會像var
一樣變數提升。 - 引數預設值:允許在函式定義的時候指定預設的值。
// Basic syntax
function multiply (a, b = 2) {
return a * b;
}
multiply(5); // 10
- 類定義和繼承
ES6開始支援定義類(使用class
關鍵字),建構函式(使用constructor
關鍵字),和extend
關鍵字來實現繼承。
- for-of操作
for...of
語句用來迭代訪問一個物件的所有屬性。
- Spread操作符:用於物件合併
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2, c: 3, d: 4}
const obj3 = {...obj1, ...obj2}
- Promise: Promises提供了一個處理非同步操作的方法。你可以用回撥函式來實現,但是Promise更加簡潔和可讀。
const isGreater = (a, b) => {
return new Promise ((resolve, reject) => {
if(a > b) {
resolve(true)
} else {
reject(false)
}
})
}
isGreater(1, 2)
.then(result => {
console.log('greater')
})
.catch(result => {
console.log('smaller')
})
- 模組的export和import。
const myModule = { x: 1, y: () => { console.log('This is ES5') }}
export default myModule;
import myModule from './myModule';
來源: Bulby.io
Q15: 請解釋undefined
和not defined
的區別
話題: JavaScript 難度: ⭐⭐⭐
在JavaScript中,如果你嘗試使用不存在的還未申明的變數,JavaScript會丟擲錯誤var name is not defined
。但是如果你用typeof
來檢視其型別,會返回undefined
。
我們先來澄清一下宣告和定義的區別:var x
是一個宣告,因為你並沒有定義其具體的值,你只是宣告其存在性。
var x; // declaring x
console.log(x); //output: undefined
var x = 1
同時兼具宣告和定義,我們也可以叫它初始化。在JavaScript中,每一個變數和函式宣告都會被提升到頂部。
如果我們訪問一個聲明瞭但是未定義的變數,會返回undefined
。
var x; // Declaration
if(typeof x === 'undefined') // Will return true
訪問一個未宣告未定義的變數,會返回not defined錯誤。
console.log(y); // Output: ReferenceError: y is not defined
Q16: 匿名函式和命名函式的區別?
話題: JavaScript 難度: ⭐⭐⭐
var foo = function() { // anonymous function assigned to variable foo
// ..
};
var x = function bar(){ // named function (bar) assigned to variable x
// ..
};
foo(); // actual function execution
x();
譯者補充:匿名函式如果不賦值給某個變數,則無法被呼叫了;命名函式再次被賦值不是多此一舉麼。
Q17: JavaScript中閉包是什麼?請提供一個例子
話題: JavaScript 難度: ⭐⭐⭐⭐
閉包是一個定義在其它函式(父函式)裡面的函式,它擁有對父函式裡面變數的訪問權。閉包擁有如下三個作用域的訪問權:
- 自身的作用域
- 父作用域
- 全域性作用域
var globalVar = "abc";
// Parent self invoking function
(function outerFunction (outerArg) { // begin of scope outerFunction
// Variable declared in outerFunction function scope
var outerFuncVar = 'x';
// Closure self-invoking function
(function innerFunction (innerArg) { // begin of scope innerFunction
// variable declared in innerFunction function scope
var innerFuncVar = "y";
console.log(
"outerArg = " + outerArg + "\n" +
"outerFuncVar = " + outerFuncVar + "\n" +
"innerArg = " + innerArg + "\n" +
"innerFuncVar = " + innerFuncVar + "\n" +
"globalVar = " + globalVar);
// end of scope innerFunction
})(5); // Pass 5 as parameter
// end of scope outerFunction
})(7); // Pass 7 as parameter
innerFunction
是一個閉包,定義在outerFunction
中,它可以訪問outerFunction
作用域的所有變數。當然,它還可以訪問全域性變數。
輸出結果如下:
outerArg = 7
outerFuncVar = x
innerArg = 5
innerFuncVar = y
globalVar = abc
Q18: 在JavaScript中如何建立私有變數?
話題: JavaScript 難度: ⭐⭐⭐⭐
你可以通過在函式中宣告變數來建立私有變數。因為在函式中,外部無法直接訪問。
function func() {
var priv = "secret code";
}
console.log(priv); // throws error
為了訪問該變數,可以構造一個幫助函式來返回該值。
function func() {
var priv = "secret code";
return function() {
return priv;
}
}
var getPriv = func();
console.log(getPriv()); // => secret code
Q19: 請解釋原型模式(Prototype Design Pattern)
話題: JavaScript 難度: ⭐⭐⭐⭐
原型模式會建立一個新的物件,但不是建立一個未初始化的物件,而是通過拷貝原型鏈上的值或則被拷貝物件的值來完成初始化。傳統的語言很少使用原型模式,但是JavaScript作為一個基於原型的語言,使用原型模式來建立新的物件。
來源: dofactory.com
Q20: 判斷給定的字串是否同態(isomorphic)
話題: JavaScript 難度: ⭐⭐⭐⭐
首先介紹什麼叫做同態:兩個字串,如果A字串中的每一個字元都可以在B字串中找到唯一對應,並且順序一一對應;如果存在這樣的函式,那麼A和B同態。
paper
和title
同態egg
和sad
不同態dgg
和add
同態
isIsomorphic("egg", 'add'); // true
isIsomorphic("paper", 'title'); // true
isIsomorphic("kick", 'side'); // false
function isIsomorphic(firstString, secondString) {
// Check if the same length. If not, they cannot be isomorphic
if (firstString.length !== secondString.length) return false
var letterMap = {};
for (var i = 0; i < firstString.length; i++) {
var letterA = firstString[i],
letterB = secondString[i];
// If the letter does not exist, create a map and map it to the value
// of the second letter
if (letterMap[letterA] === undefined) {
letterMap[letterA] = letterB;
} else if (letterMap[letterA] !== letterB) {
// Eles if letterA already exists in the map, but it does not map to
// letterB, that means that A is mapping to more than one letter.
return false;
}
}
// If after iterating through and conditions are satisfied, return true.
// They are isomorphic
return true;
}
Q21: Transpiling
代表了什麼意思?
話題: JavaScript 難度: ⭐⭐⭐⭐
Transpiling
是transforming + compiling
的合成詞。對於一些新的語法,瀏覽器還不支援。最好的辦法就是將其變換到舊的等價的程式碼,這個過程通常叫做transpiling
。
典型的,你可以在build
的過程中加入transpiler
,就如同code linter
和minifier
一樣。
已經有很多知名的transpilers可供使用:
- Babel: 將ES6編譯到ES5
- Traceur:將ES6,ES7等編譯到ES5
來源: You Don't Know JS, Up &going
Q22: this
關鍵字如何工作?請提供一些例子
話題: JavaScript 難度: ⭐⭐⭐⭐
在JavaScript中,this總是指向函式的“擁有者”(也就是指向該函式的物件),或則擁有該函式的物件。
function foo() {
console.log( this.bar );
}
var bar = "global";
var obj1 = {
bar: "obj1",
foo: foo
};
var obj2 = {
bar: "obj2"
};
foo(); // "global"
obj1.foo(); // "obj1"
foo.call( obj2 ); // "obj2"
new foo(); // undefined
來源: quirksmode.org
Q23: 如何為Array物件新增你自定義的函式,使得如下程式碼可以正常工作。
話題: JavaScript 難度: ⭐⭐⭐⭐
var arr = [1, 2, 3, 4, 5];
var avg = arr.average();
console.log(avg);
JavaScript是一個基於原型的語言。也就是說物件之間通過原型連結,並繼承其函式。為了給Array物件新增函式,我們可以修改其原型定義Array prorotype
。
Array.prototype.average = function() {
// calculate sum
var sum = this.reduce(function(prev, cur) { return prev + cur; });
// return sum divided by number of elements
return sum / this.length;
}
var arr = [1, 2, 3, 4, 5];
var avg = arr.average();
console.log(avg); // => 3
來源: coderbyte.com
Q24: JavaScript中提升(hoisting)是什麼意思?
話題: JavaScript 難度: ⭐⭐⭐⭐
提升(hoisting)是指JavaScript的直譯器將所有的變數和函式宣告都提升到該作用域的頂部,有兩種提升型別:
- 變數提升
- 函式提升
在一個作用域中通過宣告的變數和函式在整個作用域中都可以使用。
var a = 2;
foo(); // works because `foo()`
// declaration is "hoisted"
function foo() {
a = 3;
console.log( a ); // 3
var a; // declaration is "hoisted"
// to the top of `foo()`
}
console.log( a ); // 2
雖然foo()
函式在後面定義,但是在前面也可以呼叫。
Q25: 如下程式碼會返回什麼結果?
話題: JavaScript 難度: ⭐⭐⭐⭐
0.1 + 0.2 === 0.3
不要驚訝,其結果是false。因為浮點數在系統內的精確度問題,0.1+0.2的結果並不是0.3,而是0.30000000000000004。 要避免這個問題的方法是指定返回結果的小數位數。
來源: coderbyte.com
Q26: 請描述一下揭示模式(Revealing Module Pattern)
話題: JavaScript 難度: ⭐⭐⭐⭐⭐
Module pattern的一個變種是Revealing Module Pattern
。該設計模式的目的是做到很好的程式碼隔離,只是將需要對外公開的變數和函式暴露出來。一個直接的實現如下所示:
var Exposer = (function() {
var privateVariable = 10;
var privateMethod = function() {
console.log('Inside a private method!');
privateVariable++;
}
var methodToExpose = function() {
console.log('This is a method I want to expose!');
}
var otherMethodIWantToExpose = function() {
privateMethod();
}
return {
first: methodToExpose,
second: otherMethodIWantToExpose
};
})();
Exposer.first(); // Output: This is a method I want to expose!
Exposer.second(); // Output: Inside a private method!
Exposer.methodToExpose; // undefined
來源: scotch.io