根據 Stack Overflow 的 2018 年度調查,JavaScript 連續六年成爲最經常使用的編程語言。因此咱們必須面對這樣的現實,JavaScript 已經成爲全棧開發技能的基石,在全棧開發面試中都會不可避免地涉及到與 JavaScript 有關的問題。FullStack.Cafe 彙編了最多見的 JavaScript 面試問題和答案,但願可以幫助讀者找到下一份夢想中的工做。面試
難度:0算法
在 JavaScript 中,兩種不一樣的內置類型間的轉換被稱爲強制轉型。強制轉型在 JavaScript 中有兩種形式:顯式和隱式。數據庫
這是一個顯式強制轉型的例子:編程
var a = "42"; var b = Number( a ); a; // "42" b; // 42 -- 是個數字!
這是一個隱式強制轉型的例子:設計模式
var a = "42"; var b = a * 1; // "42" 隱式轉型成 42 a; // "42" b; // 42 -- 是個數字!
難度:⭐數組
在 JavaScript 中,每一個函數都有本身的做用域。做用域基本上是變量以及如何經過名稱訪問這些變量的規則的集合。只有函數中的代碼才能訪問函數做用域內的變量。promise
同一個做用域中的變量名必須是惟一的。一個做用域能夠嵌套在另外一個做用域內。若是一個做用域嵌套在另外一個做用域內,最內部做用域內的代碼能夠訪問另外一個做用域的變量。瀏覽器
難度:⭐安全
JavaScript 中有嚴格比較和類型轉換比較:閉包
var a = "42"; var b = 42; a == b; // true a === b; // false
一些簡單的規則:
難度:⭐⭐
回調函數是能夠做爲參數傳遞給另外一個函數的函數,並在某些操做完成後執行。下面是一個簡單的回調函數示例,這個函數在某些操做完成後打印消息到控制檯。
function modifyArray(arr, callback) { // 對 arr 作一些操做 arr.push(100); // 執行傳進來的 callback 函數 callback(); } var arr = [1, 2, 3, 4, 5]; modifyArray(arr, function() { console.log("array has been modified", arr); });
難度:⭐⭐
use strict 出如今 JavaScript 代碼的頂部或函數的頂部,能夠幫助你寫出更安全的 JavaScript 代碼。若是你錯誤地建立了全局變量,它會經過拋出錯誤的方式來警告你。例如,如下程序將拋出錯誤:
function doSomething(val) { "use strict"; x = val + 10; }
它會拋出一個錯誤,由於 x 沒有被定義,並使用了全局做用域中的某個值對其進行賦值,而 use strict 不容許這樣作。下面的小改動修復了這個錯誤:
function doSomething(val) { "use strict"; var x = val + 10; }
難度:⭐⭐
JavaScript 中有兩種底層類型:null 和 undefined。它們表明了不一樣的含義:
難度:⭐⭐
var addSix = createBase(6); addSix(10); // 返回 16 addSix(21); // 返回 27
能夠建立一個閉包來存放傳遞給函數 createBase 的值。被返回的內部函數是在外部函數中建立的,內部函數就成了一個閉包,它能夠訪問外部函數中的變量,在本例中是變量 baseNumber。
function createBase(baseNumber) { return function(N) { // 咱們在這裏訪問 baseNumber,即便它是在這個函數以外聲明的。 // JavaScript 中的閉包容許咱們這麼作。 return baseNumber + N; } } var addSix = createBase(6); addSix(10); addSix(21);
難度:⭐⭐
難度:⭐⭐
事件冒泡是指嵌套最深的元素觸發一個事件,而後這個事件順着嵌套順序在父元素上觸發。
防止事件冒泡的一種方法是使用 event.cancelBubble 或 event.stopPropagation()(低於 IE 9)。
難度:⭐⭐
除了能夠在函數級別聲明變量以外,ES6 還容許你使用 let 關鍵字在代碼塊({..})中聲明變量。
難度:⭐⭐
檢查一個數字是小數仍是整數,可使用一種很是簡單的方法,就是將它對 1 進行取模,看看是否有餘數。
function isInt(num) { return num % 1 === 0; } console.log(isInt(4)); // true console.log(isInt(12.2)); // false console.log(isInt(0.3)); // false
難度:⭐⭐⭐
它是當即調用函數表達式(Immediately-Invoked Function Expression),簡稱 IIFE。函數被建立後當即被執行:
(function IIFE(){ console.log( "Hello!" ); })(); // "Hello!"
在避免污染全局命名空間時常用這種模式,由於 IIFE(與任何其餘正常函數同樣)內部的全部變量在其做用域以外都是不可見的。
難度:⭐⭐⭐
對於兩個非原始值,好比兩個對象(包括函數和數組),== 和 === 比較都只是檢查它們的引用是否匹配,並不會檢查實際引用的內容。
例如,默認狀況下,數組將被強制轉型成字符串,並使用逗號將數組的全部元素鏈接起來。因此,兩個具備相同內容的數組進行 == 比較時不會相等:
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 這個庫,或者本身實現遞歸比較算法。
難度:⭐⭐⭐
const greetings = (name) => { return `hello ${name}`; } const greetings = name => `hello ${name}`;
常量在不少方面與其餘語言中的常量同樣,但有一些須要注意的地方。常量表示對值的「固定引用」。所以,在使用常量時,你實際上能夠改變變量所引用的對象的屬性,但沒法改變引用自己。
const NAMES = []; NAMES.push("Jim"); console.log(NAMES.length === 1); // true NAMES = ["Steve", "John"]; // error
新的 ES6 關鍵字 let 容許開發人員聲明塊級別做用域的變量。let 不像 var 那樣能夠進行提高。
默認參數容許咱們使用默認值初始化函數。若是省略或未定義參數,則使用默認值,也就是說 null 是有效值。
// 基本語法 function multiply (a, b = 2) { return a * b; } multiply(5); // 10
ES6 引入了對類(關鍵字 class)、構造函數(關鍵字 constructor)和用於繼承的 extend 關鍵字的支持。
for…of 語句將建立一個遍歷可迭代對象的循環。
const obj1 = { a: 1, b: 2 } const obj2 = { a: 2, c: 3, d: 4} const obj3 = {...obj1, ...obj2}
promise 提供了一種機制來處理異步操做結果。你可使用回調來達到一樣的目的,可是 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') })
const myModule = { x: 1, y: () => { console.log('This is ES5') }} export default myModule; import myModule from './myModule';
難度:⭐⭐⭐
在 JavaScript 中,若是你試圖使用一個不存在且還沒有聲明的變量,JavaScript 將拋出錯誤「var name is not defined」,讓後腳本將中止運行。但若是你使用 typeof undeclared_variable,它將返回 undefined。
在進一步討論以前,先讓咱們理解聲明和定義之間的區別。
「var x」表示一個聲明,由於你沒有定義它的值是什麼,你只是聲明它的存在。
var x; // 聲明 x console.log(x); // 輸出: undefined
「var x = 1」既是聲明又是定義(咱們也能夠說它是初始化),x 變量的聲明和賦值相繼發生。在 JavaScript 中,每一個變量聲明和函數聲明都被帶到了當前做用域的頂部,而後進行賦值,這個過程被稱爲提高(hoisting)。
當咱們試圖訪問一個被聲明但未被定義的變量時,會出現 undefined 錯誤。
var x; // 聲明 if(typeof x === 'undefined') // 將返回 true
當咱們試圖引用一個既未聲明也未定義的變量時,將會出現 not defined 錯誤。
console.log(y); // 輸出: ReferenceError: y is not defined
難度:⭐⭐⭐
var foo = function() { // 賦給變量 foo 的匿名函數 // .. }; var x = function bar(){ // 賦給變量 x 的命名函數 bar // .. }; foo(); // 實際執行函數 x();
難度:⭐⭐⭐⭐
閉包是在另外一個函數(稱爲父函數)中定義的函數,而且能夠訪問在父函數做用域中聲明和定義的變量。
閉包能夠訪問三個做用域中的變量:
var globalVar = "abc"; // 自調用函數 (function outerFunction (outerArg) { // outerFunction 做用域開始 // 在 outerFunction 函數做用域中聲明的變量 var outerFuncVar = 'x'; // 閉包自調用函數 (function innerFunction (innerArg) { // innerFunction 做用域開始 // 在 innerFunction 函數做用域中聲明的變量 var innerFuncVar = "y"; console.log( "outerArg = " + outerArg + "\n" + "outerFuncVar = " + outerFuncVar + "\n" + "innerArg = " + innerArg + "\n" + "innerFuncVar = " + innerFuncVar + "\n" + "globalVar = " + globalVar); // innerFunction 做用域結束 })(5); // 將 5 做爲參數 // outerFunction 做用域結束 })(7); // 將 7 做爲參數
innerFunction 是在 outerFunction 中定義的閉包,能夠訪問在 outerFunction 做用域內聲明和定義的全部變量。除此以外,閉包還能夠訪問在全局命名空間中聲明的變量。
上述代碼的輸出將是:
outerArg = 7 outerFuncVar = x innerArg = 5 innerFuncVar = y globalVar = abc
難度:⭐⭐⭐⭐
要在 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
難度:⭐⭐⭐⭐
原型模式可用於建立新對象,但它建立的不是非初始化的對象,而是使用原型對象(或樣本對象)的值進行初始化的對象。原型模式也稱爲屬性模式。
原型模式在初始化業務對象時很是有用,業務對象的值與數據庫中的默認值相匹配。原型對象中的默認值被複制到新建立的業務對象中。
經典的編程語言不多使用原型模式,但做爲原型語言的 JavaScript 在構造新對象及其原型時使用了這個模式。
難度:⭐⭐⭐⭐
若是兩個字符串是同構的,那麼字符串 A 中全部出現的字符均可以用另外一個字符替換,以便得到字符串 B,並且必須保留字符的順序。字符串 A 中的每一個字符必須與字符串 B 的每一個字符一對一對應。
isIsomorphic("egg", 'add'); // true isIsomorphic("paper", 'title'); // true isIsomorphic("kick", 'side'); // false function isIsomorphic(firstString, secondString) { // 檢查長度是否相等,若是不相等, 它們不多是同構的 if (firstString.length !== secondString.length) return false var letterMap = {}; for (var i = 0; i < firstString.length; i++) { var letterA = firstString[i], letterB = secondString[i]; // 若是 letterA 不存在, 建立一個 map,並將 letterB 賦值給它 if (letterMap[letterA] === undefined) { letterMap[letterA] = letterB; } else if (letterMap[letterA] !== letterB) { // 若是 letterA 在 map 中已存在, 但不是與 letterB 對應, // 那麼這意味着 letterA 與多個字符相對應。 return false; } } // 迭代完畢,若是知足條件,那麼返回 true。 // 它們是同構的。 return true; }
難度:⭐⭐⭐⭐
對於語言中新加入的語法,沒法進行 polyfill。所以,更好的辦法是使用一種工具,能夠將較新代碼轉換爲較舊的等效代碼。這個過程一般稱爲轉換(transpiling),就是 transforming + compiling 的意思。
一般,你會將轉換器(transpiler)加入到構建過程當中,相似於 linter 或 minifier。如今有不少很棒的轉換器可選擇:
難度:⭐⭐⭐⭐
在 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
難度:⭐⭐⭐⭐
var arr = [1, 2, 3, 4, 5]; var avg = arr.average(); console.log(avg);
JavaScript 不是基於類的,但它是基於原型的語言。這意味着每一個對象都連接到另外一個對象(也就是對象的原型),並繼承原型對象的方法。你能夠跟蹤每一個對象的原型鏈,直到到達沒有原型的 null 對象。咱們須要經過修改 Array 原型來向全局 Array 對象添加方法。
Array.prototype.average = function() { // 計算 sum 的值 var sum = this.reduce(function(prev, cur) { return prev + cur; }); // 將 sum 除以元素個數並返回 return sum / this.length; } var arr = [1, 2, 3, 4, 5]; var avg = arr.average(); console.log(avg); // => 3
難度:⭐⭐⭐⭐
提高(hoisting)是 JavaScript 解釋器將全部變量和函數聲明移動到當前做用域頂部的操做。有兩種類型的提高:
不管 var(或函數聲明)出如今做用域的什麼地方,它都屬於整個做用域,而且能夠在該做用域內的任何地方訪問它。
var a = 2; foo(); // 由於`foo()`聲明被"提高",因此可調用 function foo() { a = 3; console.log( a ); // 3 var a; // 聲明被"提高"到 foo() 的頂部 } console.log( a ); // 2
難度:⭐⭐⭐⭐
0.1 + 0.2 === 0.3
這段代碼的輸出是 false,這是由浮點數內部表示致使的。0.1 + 0.2 並不恰好等於 0.3,實際結果是 0.30000000000000004。解決這個問題的一個辦法是在對小數進行算術運算時對結果進行舍入。
難度:⭐⭐⭐⭐⭐
暴露模塊模式(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(); // 輸出: This is a method I want to expose! Exposer.second(); // 輸出: Inside a private method! Exposer.methodToExpose; // undefined
它的一個明顯的缺點是沒法引用私有方法。