2018年,最經典的26個JavaScript面試題和答案!

根據 Stack Overflow 的 2018 年度調查,JavaScript 連續六年成爲最經常使用的編程語言。因此咱們必須面對這樣的現實,JavaScript 已經成爲全棧開發技能的基石,在全棧開發面試中都會不可避免地涉及到與 JavaScript 有關的問題。FullStack.Cafe 彙編了最多見的 JavaScript 面試問題和答案,但願可以幫助讀者找到下一份夢想中的工做。面試

Q1:JavaScript 中的強制轉型(coercion)是指什麼?

難度: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 -- 是個數字!

Q2:JavaScript 中的做用域(scope)是指什麼?

難度:⭐數組

在 JavaScript 中,每一個函數都有本身的做用域。做用域基本上是變量以及如何經過名稱訪問這些變量的規則的集合。只有函數中的代碼才能訪問函數做用域內的變量。promise

同一個做用域中的變量名必須是惟一的。一個做用域能夠嵌套在另外一個做用域內。若是一個做用域嵌套在另外一個做用域內,最內部做用域內的代碼能夠訪問另外一個做用域的變量。瀏覽器

Q3:解釋 JavaScript 中的相等性。

難度:⭐安全

JavaScript 中有嚴格比較和類型轉換比較:閉包

  • 嚴格比較(例如 ===)在不容許強制轉型的狀況下檢查兩個值是否相等;
  • 抽象比較(例如 ==)在容許強制轉型的狀況下檢查兩個值是否相等。
var a = "42";
var b = 42;
a == b;            // true
a === b;        // false

一些簡單的規則:

  • 若是被比較的任何一個值多是 true 或 false,要用 ===,而不是 ==;
  • 若是被比較的任何一個值是這些特定值(0、「」或 []),要用 ===,而不是 ==;
  • 在其餘狀況下,能夠安全地使用 ==。它不只安全,並且在不少狀況下,它能夠簡化代碼,而且提高代碼可讀性。

Q4:解釋什麼是回調函數,並提供一個簡單的例子。

難度:⭐⭐

回調函數是能夠做爲參數傳遞給另外一個函數的函數,並在某些操做完成後執行。下面是一個簡單的回調函數示例,這個函數在某些操做完成後打印消息到控制檯。

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);
});

Q5:「use strict」的做用是什麼?

難度:⭐⭐

use strict 出如今 JavaScript 代碼的頂部或函數的頂部,能夠幫助你寫出更安全的 JavaScript 代碼。若是你錯誤地建立了全局變量,它會經過拋出錯誤的方式來警告你。例如,如下程序將拋出錯誤:

function doSomething(val) {
  "use strict"; 
  x = val + 10;
}

它會拋出一個錯誤,由於 x 沒有被定義,並使用了全局做用域中的某個值對其進行賦值,而 use strict 不容許這樣作。下面的小改動修復了這個錯誤:

function doSomething(val) {
  "use strict"; 
  var x = val + 10;
}

Q6:解釋 JavaScript 中的 null 和 undefined。

難度:⭐⭐

JavaScript 中有兩種底層類型:null 和 undefined。它們表明了不一樣的含義:

  • 還沒有初始化的東西:undefined;
  • 目前不可用的東西:null。

Q7:編寫一個能夠執行以下操做的函數。

難度:⭐⭐

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);

Q8:解釋 JavaScript 中的值和類型。

難度:⭐⭐

  • JavaScript 有類型值,但沒有類型變量。JavaScript 提供瞭如下幾種內置類型:
  • string
  • number
  • boolean
  • null 和 undefined
  • object
  • symbol (ES6 中新增的)

Q9:解釋事件冒泡以及如何阻止它?

難度:⭐⭐

事件冒泡是指嵌套最深的元素觸發一個事件,而後這個事件順着嵌套順序在父元素上觸發。

防止事件冒泡的一種方法是使用 event.cancelBubble 或 event.stopPropagation()(低於 IE 9)。

Q10:JavaScript 中的 let 關鍵字有什麼用?

難度:⭐⭐

除了能夠在函數級別聲明變量以外,ES6 還容許你使用 let 關鍵字在代碼塊({..})中聲明變量。

Q11:如何檢查一個數字是否爲整數?

難度:⭐⭐

檢查一個數字是小數仍是整數,可使用一種很是簡單的方法,就是將它對 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

Q12:什麼是 IIFE(當即調用函數表達式)?

難度:⭐⭐⭐

它是當即調用函數表達式(Immediately-Invoked Function Expression),簡稱 IIFE。函數被建立後當即被執行:

(function IIFE(){
    console.log( "Hello!" );
})();
// "Hello!"

在避免污染全局命名空間時常用這種模式,由於 IIFE(與任何其餘正常函數同樣)內部的全部變量在其做用域以外都是不可見的。

Q13:如何在 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 之間的區別嗎?

難度:⭐⭐⭐

  • ECMAScript 5(ES5):ECMAScript 的第 5 版,於 2009 年標準化。這個標準已在全部現代瀏覽器中徹底實現。
  • ECMAScript 6(ES6)或 ECMAScript 2015(ES2015):第 6 版 ECMAScript,於 2015 年標準化。這個標準已在大多數現代瀏覽器中部分實現。
  • 如下是 ES5 和 ES6 之間的一些主要區別:
  • 箭頭函數和字符串插值:
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 操做符

for…of 語句將建立一個遍歷可迭代對象的循環。

  • 用於對象合併的 Spread 操做
const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2, c: 3, d: 4}
const obj3 = {...obj1, ...obj2}
  • promise

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';

問題 15:解釋 JavaScript 中「undefined」和「not defined」之間的區別。

難度:⭐⭐⭐

在 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

Q16:匿名和命名函數有什麼區別?

難度:⭐⭐⭐

var foo = function() { // 賦給變量 foo 的匿名函數
    // ..
};

var x = function bar(){ // 賦給變量 x 的命名函數 bar
    // ..
};

foo(); // 實際執行函數
x();

Q17:Javascript 中的「閉包」是什麼?舉個例子?

難度:⭐⭐⭐⭐

閉包是在另外一個函數(稱爲父函數)中定義的函數,而且能夠訪問在父函數做用域中聲明和定義的變量。

閉包能夠訪問三個做用域中的變量:

  • 在本身做用域中聲明的變量;
  • 在父函數中聲明的變量;
  • 在全局做用域中聲明的變量。
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

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:請解釋原型設計模式。

難度:⭐⭐⭐⭐

原型模式可用於建立新對象,但它建立的不是非初始化的對象,而是使用原型對象(或樣本對象)的值進行初始化的對象。原型模式也稱爲屬性模式。

原型模式在初始化業務對象時很是有用,業務對象的值與數據庫中的默認值相匹配。原型對象中的默認值被複制到新建立的業務對象中。

經典的編程語言不多使用原型模式,但做爲原型語言的 JavaScript 在構造新對象及其原型時使用了這個模式。

Q20:判斷一個給定的字符串是不是同構的。

難度:⭐⭐⭐⭐

若是兩個字符串是同構的,那麼字符串 A 中全部出現的字符均可以用另外一個字符替換,以便得到字符串 B,並且必須保留字符的順序。字符串 A 中的每一個字符必須與字符串 B 的每一個字符一對一對應。

  • paper 和 title 將返回 true。
  • egg 和 sad 將返回 false。
  • dgg 和 add 將返回 true。
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;
}

Q21:「Transpiling」是什麼意思?

難度:⭐⭐⭐⭐

對於語言中新加入的語法,沒法進行 polyfill。所以,更好的辦法是使用一種工具,能夠將較新代碼轉換爲較舊的等效代碼。這個過程一般稱爲轉換(transpiling),就是 transforming + compiling 的意思。

一般,你會將轉換器(transpiler)加入到構建過程當中,相似於 linter 或 minifier。如今有不少很棒的轉換器可選擇:

  • Babel:將 ES6+ 轉換爲 ES5
  • Traceur:將 ES六、ES7 轉換爲 ES5

Q22:「this」關鍵字的原理是什麼?請提供一些代碼示例。

難度:⭐⭐⭐⭐

在 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

Q23:如何向 Array 對象添加自定義方法,讓下面的代碼能夠運行?

難度:⭐⭐⭐⭐

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

Q24:什麼是 JavaScript 中的提高操做?

難度:⭐⭐⭐⭐

提高(hoisting)是 JavaScript 解釋器將全部變量和函數聲明移動到當前做用域頂部的操做。有兩種類型的提高:

  • 變量提高——很是少見
  • 函數提高——更常見

不管 var(或函數聲明)出如今做用域的什麼地方,它都屬於整個做用域,而且能夠在該做用域內的任何地方訪問它。

var a = 2;
foo();                   // 由於`foo()`聲明被"提高",因此可調用

function foo() {
    a = 3;
    console.log( a );    // 3
    var a;               // 聲明被"提高"到 foo() 的頂部
}

console.log( a );    // 2

Q25:如下代碼輸出的結果是什麼?

難度:⭐⭐⭐⭐

0.1 + 0.2 === 0.3

這段代碼的輸出是 false,這是由浮點數內部表示致使的。0.1 + 0.2 並不恰好等於 0.3,實際結果是 0.30000000000000004。解決這個問題的一個辦法是在對小數進行算術運算時對結果進行舍入。

Q26:請描述一下 Revealing 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();        // 輸出: This is a method I want to expose!
Exposer.second();       // 輸出: Inside a private method!
Exposer.methodToExpose; // undefined

它的一個明顯的缺點是沒法引用私有方法。

原文連接:https://www.jianshu.com/p/8c3...

相關文章
相關標籤/搜索