那些年遇到的刁鑽JavaScript面試題(可防踩坑)

僅以此文記念這些年失敗的面試和逝去的頭髮。。html

第 1 題:console.log(2 + '2')

答案: '22'

解析:
這是比較常規的面試題了,主要考察的是 JavaScript 中的隱式類型轉換。在 JS 中 + 主要有兩個做用:數字相加和字符串拼接,當 + 兩邊不都爲數字時會把它們都轉爲字符串再拼接,因此第一個 2 會先被轉成 '2' 再與第二個 '2' 拼接。node

第 2 題:console.log(2 - '2')

答案: 0

解析:
+ 不一樣,- 沒有操做字符串而只有 「減法」 的功能,當 - 兩邊有非數字時會先把其轉換成數字再相減。因此,本題中的 '2' 先被轉成數字 2,最終 2 - 2 等於 0。當操做數無法轉換成數字時則會致使結果爲 NaN,好比 'foo' - 2 = NaNgit

*/% 的行爲也和 - 相似。

第 3 題:console.log(true + 1)

答案: 2

解析:
有了第1題的經驗,咱們很容易就認爲 true1 都會被轉成字符串,但實際上 JS 中 true == 1, false == 0 ,因此 true 會被轉成 1 再執行加法。github

注意: true 只等於 1false 只等於 0,等於其餘數字是不成立的,如 true == 2false

第 4 題:console.log(NaN === NaN)

答案: false

解析:
NaN 表示一個不爲數字的值(Not a number)。咱們只須要記住:NaN和全部值都不等,包括它本身,不論是用 == 仍是 === 判斷!判斷一個值是否爲 NaN 只能用 isNaN() 或者 Number.isNaN()面試

第 5 題:1. console.log(5 < 6 < 7) 2. console.log(7 > 6 > 5)

答案:(1) true;(2) false

解析:
根據第三題的經驗 true == 1false == 05 < 6 === true1 < 7,因此 1 爲 true7 > 6 === true1 > 5 === false,因此 2 爲 false算法

第 6 題:0.1 + 0.2 = ?

答案: 0.30000000000000004

解析:
問題的關鍵不在於答案裏面有幾個 0,而在於它不等於 0.3!Javascript 中的數字使用的是 64 位雙精度浮點型(可參考 ECMAScript 規範)。如同十進制不能精確表示 1/3 對應的小數,二進制也沒有辦法精確表示 0.10.2 這種小數,好比 0.1 轉成二進制爲:0.0001100110011001100110011001100110011001100110011001101,是無限循環的小數,而由於計算機不可能無限分配內存去存儲這個數,通常只能精確到多少位,因此形成精度丟失在所不免,最終致使計算結果有所誤差。express

第 7 題:[1, 2, 3] + [4, 5, 6] = ?

答案: 1,2,34,5,6

解析:
本題主要考察隱式類型轉換和數組轉字符串,咱們已經知道 + 兩邊若是不都爲數字則會把它們轉成字符串再拼接,而 [1, 2, 3].toString() === '1,2,3',由於最終結果爲 '1,2,3' + '4,5,6' === '1,2,34,5,6'數組

若是咱們想要進行數組拼接能夠:安全

[1, 2, 3].concat([4, 5, 6]);

// 或者使用spread opertator
[...[1, 2, 3], ...[4, 5, 6]];

第 8 題:求打印結果

(function () {
  var a = b = 100;
})();

console.log(a);
console.log(b);
答案:報錯( Uncaught ReferenceError: a is not defined

解析:
因爲賦值表達式是從右往左執行的,至關於 var a = (b = 100);,因此先執行 b = 100,因爲函數體中並無局部變量 b,因此會定義一個全局變量 b 並賦值 100;接着執行 a = b,會把b的值賦值給局部變量 a。執行 console.log(a) 時會直接報錯,由於全局做用域中並無定義 a,因爲報錯致使程序中斷因此 console.log(b) 沒有執行。若是把 console.log(b) 放在前面就是先打印 100 再報錯了。函數

使用嚴格模式( 'use strict')能夠避免 b這種意外全局變量的建立。

第 9 題:求打印結果

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 0);
}
答案: 5 5 5 5 5

解析:
這是個經典問題,考察對做用域和 event loop 的理解。用 var 定義的變量的做用域是函數做用域(functional scope),因此上面代碼至關於:

var i;

for (i = 0; i < 5; i++) {
  ...
}

最終 console.log(i) 裏面的 i 引用的是外面的那個,而因爲event loop機制,setTimeout 的回調函數會被push到task queue裏面等call stack裏面的for循環結束了再執行,此時 i 已經變成了5,所以最終打印結果變成5個5。隨着ES6的普及,var 使用的機會愈來愈少,letconst 成爲主流,本題若是想打印 0 1 2 3 4,只要把 var 換成 let 就好了。

第 10 題:求打印結果

function foo() {
  return x;

  function x() {}
}

console.log(typeof foo());
答案: 'function'

解析:
本題考察的是對函數提高的理解。通常定義一個函數有兩種方式:

  1. 函數聲明(function declaration):function fn(){ ... }
  2. 函數表達式(function expression):var fn = function(){ ... }

其中函數聲明會存在函數提高的現象,而函數表達式則沒有(函數表達式會存在變量提高,但初始化並不會被提高,因此不具備函數提高的效果),即JS在編譯階段會把對函數的定義提高到做用域頂部(實際上並不會修改代碼結構,而是在內存中進行處理),因此本題的代碼等價於:

function foo() {
  function x() {
    console.log(`hi`);
  }
  
  return x;
}

因此,結果打印 function。函數提高的主要做用是能夠在函數定義以前就進行調用。

第 11 題:求打印結果

var x = 1;
function x(){}

console.log(typeof x);
答案: 'number'

解析:
本題仍是考察變量提高和函數提高,以及它們的優先級。函數提高的優先級要高於變量提高,因此函數被提高到做用域最頂部,接下來纔是變量定義,所以本題等價於:

function x(){}
var x;
x = 1;

console.log(typeof x);

因此,x 最終是數字。

第 12 題:求打印結果

const fn = () => arguments;

console.log(fn("hi"));
答案:報錯 Uncaught ReferenceError: arguments is not defined

解析:
本題主要考察箭頭函數的特色。箭頭函數沒有本身的 thisarguments,而是引用的外層做用域中的,而全局沒有定義 arguments 變量,因此報錯。

在箭頭函數中若是要訪問參數集,建議使用 Rest parameters: (...args) => { }

第 13 題:求打印結果

const fn = function () {
  return
  {
    message: "hello";
  }
};

console.log(fn());
答案: undefined

解析:

在 JavaScript 中,若是 return 關鍵詞和返回值之間存在換行符(Line Terminator),則 return 後面會自動插入 ';',參考 ASI (Automatic semicolon insertion)。因此本題代碼等同於:

const fn = function () {
  return;
  {
    message: "hello";
  }
};

console.log(fn());

結果所以爲 undefined

第 14 題:求打印結果

setTimeout(() => {
  console.log("a");
}, 1);

setTimeout(() => {
  console.log("b");
}, 0);
答案:有多是 'a' 'b',也有多是 'b' 'a',取決於 js 運行環境。
  • 在 Node.js 中,0ms1ms是等價的,由於 0 會被轉成 1(可參考Node源碼),因此在 node 中運行結果是 'a' 'b'
  • Chrome 和 node 相似,結果也是 'a' 'b'
  • Firefox 中會打印 'b' 'a'

該題屬於「回」字有多少種寫法那一類的,並沒有多大的實際價值 😢。

第 15 題:event.targetevent.currentTarget 的區別

答案: event.target 是真正觸發 event 的元素,而 event.currentTarget 是綁定 event handler 的元素。

例如:

<div id="container">
  <button>click me</button>
</div>
const container = document.getElementById("container");
container.addEventListener("click", function (e) {
  console.log("target =", e.target);
  console.log("currentTarget =", e.currentTarget);
});

target

第 16 題:求打印結果

function(){
  console.log('hi');
}()
答案:報錯: Uncaught SyntaxError: Function statements require a function name

解析:
本題主要考察對 IIFE 語法的理解。本題代碼等價於:

function(){
  console.log('hi');
}

()

因此報語法錯誤,而正確的 IIFE 語法應該是 (function(){...})()

第 17 題:求打印結果

const arr = [1, 2, 3];
arr[-1] = -1;
console.log(arr[arr.indexOf(100)]);
答案: -1

解析:
本題主要考察對 JavaScript 對象的理解和數組的 indexOf() 方法。首先,數組本質仍是一個 JavaScript 對象,那就能夠設置 屬性,就算數組的索引沒有 -1,但 -1 仍可做爲對象的 key 存在,因此 x[-1] = -1 沒有問題。接着,indexOf() 方法所要查找的值若是在數組中不存在則返回 -1,因此最終至關於求 console.log(arr[-1]),獲得最終答案爲 -1

第 18 題:求數組排序後的結果

const arr = [5, 22, 1, 14, 2, 56, 132, 88, 12];
console.log(arr.sort());
答案: [1, 12, 132, 14, 2, 22, 5, 56, 88]

解析:

本題主要考察對數組 sort() 方法的理解。 sort() 默認是把元素轉成字符串,再比較 UTF-16 編碼的單元值序列進行升序排列。好比 212 的 UTF-16 編碼分別爲 5049, 50,而 49 < 50,因此 12 排在 2 以前。

若是想按照實際的數字大小升排列須要傳入一個比較函數:

// 升序
arr.sort((a, b) => a - b);
// 降序
arr.sort((a, b) => b - a);

第 19 題:求 x 的值使下列等式同時爲 true

x * x === 0;
x + 1 === 1;
x - 1 === -1;
x / x === 1;
答案: Number.MIN_VALUE

解析:

Number.MIN_VALUE 是 JavaScript 能表示的最小的正數,也是最接近 0 的值,因此不少行爲和 0 相似,例如前 3 條等式,可是它畢竟不是 0,因此能夠做爲除數,所以等式 4 也成立。與之相對的還有 Number.MAX_VALUE,是 JavaScript 中能表示的最大數。

第 20 題:console.log(9999999999999999)

答案: 10000000000000000

解析:

看到答案有點懵,直覺告訴咱們這確定又和 JavaScript 中的某些最大數的限制有關。沒錯,JS 中有個 Number.MAX_SAFE_INTEGER,它的值爲 2^53 - 1,即 9007199254740991。這個數的存在仍是由於 JS 使用的 64 位雙精度浮點型數,它能表示的區間僅僅爲 -(2^53 - 1) ~ 2^53 - 1,超過這個區間的數就不「安全」了,不安全表現爲沒法準確的表示和比較這些數,好比 Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 結果爲 trueNumber.isSafeInteger() 能夠用來判斷一個數是否 「安全」。

當咱們須要使用更大的數時建議使用 BigInt

第 21 題:原型鏈的頂層是什麼?

答案:null

解析:

通常認爲原型鏈頂層是 Object.prototype,但其實 Object.prototype 仍是有 __proto__ 的內部屬性的,而 Object.prototype.__proto__ 等於 null。因此答案爲 null 更爲準確。

參考 Annotated ECMAScript 5.1 - Properties of the Object Prototype Object

第 22 題:如何阻止給一個對象設置屬性

好比:

const obj = {};

// todo: 讓 obj.p = 1 無效

obj.p = 1;

答案:
至少有四種方法:

  1. Object.freeze(obj)
  2. Object.seal(obj)
  3. Object.preventExtensions(obj)
  4. Object.defineProperty(obj, 'p', { writable: false })

解析:

  1. Object.freeze() 最爲嚴格,它會徹底禁止對象作任何修改,包括:增長新屬性、修改已有屬性、修改其原型
  2. Object.seal() 的規則寬鬆一點:容許修改 writable 的屬性,但不容許新增和刪除屬性,且已有屬性都會被標記爲不可配置的(non-configurable)
  3. Object.preventExtensions() 更加寬鬆,能夠阻止對象新增屬性和修改其 __proto__(不能給 __proto__ 從新賦值)
  4. Object.defineProperty() 將屬性 p 定義爲不可寫的,所以沒法再給 p 設置新的值(writable 默認爲 false,能夠省略)

第 23 題:判斷一個字符串是否爲迴文(palindrome,翻轉事後和原來相等),忽略大小寫

基礎算法題,至少有2種方法:

解法1:將數字轉成字符串,再轉成數組,翻轉後再比較:

function palindrome(str) {
  str = str.toLowerCase();
  return str.split("").reverse().join("") === str;
}

解法2:for循環,頭尾比較

function palindrome(str) {
  for (var i = 0; i < str.length / 2; i++) {
    const left = str[i];
    const right = str[str.length - 1 - i];
    if (left.toLowerCase() !== right.toLowerCase()) return false;
  }

  return true;
}

這道題的升級版,是判斷一個數字是否爲迴文,且不能將數字轉成字符串。思路是經過取餘的方法獲取到每一位的數字,再構造一個反過來的數和原數進行比較:

function palindrome(num) {
  let copy = num;
  let currentDigit = 0;
  let reversedNum = 0;
  do {
    currentDigit = copy % 10;
    reversedNum = reversedNum * 10 + currentDigit;
    copy = parseInt(copy / 10);
  } while (copy !== 0);

  return num === reversedNum;
}

好了,先想到這些。若是本文對你有幫助,給個贊吧!

相關文章
相關標籤/搜索