163study

25個最基本的JavaScript面試問題及答案

本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

1.使用?typeof bar === "object"?來確定?bar?是否是對象的潛在陷阱是什么?如何避免這個陷阱?

盡管?typeof bar === "object"?是檢查?bar?是否對象的可靠方法,令人驚訝的是在JavaScript中?null?也被認為是對象!

因此,令大多數開發人員驚訝的是,下面的代碼將輸出?true?(而不是false) 到控制臺:

var bar = null;
console.log(typeof bar === "object");  // logs true!

只要清楚這一點,同時檢查?bar?是否為?null,就可以很容易地避免問題:

console.log((bar !== null) && (typeof bar === "object"));  // logs false

要答全問題,還有其他兩件事情值得注意:

首先,上述解決方案將返回?false,當?bar?是一個函數的時候。在大多數情況下,這是期望行為,但當你也想對函數返回?true?的話,你可以修改上面的解決方案為:

console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function")));

第二,上述解決方案將返回?true,當?bar?是一個數組(例如,當?var bar = [];)的時候。在大多數情況下,這是期望行為,因為數組是真正的對象,但當你也想對數組返回?false?時,你可以修改上面的解決方案為:

console.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));

或者,如果你使用jQuery的話:

console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));

2.下面的代碼將輸出什么到控制臺,為什么?

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

console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

由于?a?和?b?都定義在函數的封閉范圍內,并且都始于?var關鍵字,大多數JavaScript開發人員期望?typeof a?和?typeof b?在上面的例子中都是undefined。

然而,事實并非如此。這里的問題是,大多數開發人員將語句?var a = b = 3;?錯誤地理解為是以下聲明的簡寫:

var b = 3;
var a = b;

但事實上,var a = b = 3;?實際是以下聲明的簡寫:

b = 3;
var a = b;

因此(如果你不使用嚴格模式的話),該代碼段的輸出是:

a defined? false
b defined? true

但是,?b?如何才能被定義在封閉函數的范圍之外呢?是的,既然語句?var a = b = 3;?是語句?b = 3;?和?var a = b;的簡寫,?b?最終成為了一個全局變量(因為它沒有前綴?var?關鍵字),因此仍然在范圍內甚至封閉函數之外。

需要注意的是,在嚴格模式下(即使用?use strict),語句var a = b = 3;?將生成ReferenceError: b is not defined的運行時錯誤,從而避免任何否則可能會導致的headfakes /bug。 (還是你為什么應該理所當然地在代碼中使用?use strict?的最好例子!)

3.下面的代碼將輸出什么到控制臺,為什么?

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

上面的代碼將輸出以下內容到控制臺:

outer func:  this.foo = bar
outer func:  self.foo = bar
inner func:  this.foo = undefined
inner func:  self.foo = bar

在外部函數中,?this?和self?兩者都指向了?myObject,因此兩者都可以正確地引用和訪問?foo

在內部函數中,?this?不再指向?myObject。其結果是,this.foo?沒有在內部函數中被定義,相反,指向到本地的變量self?保持在范圍內,并且可以訪問。 (在ECMA 5之前,在內部函數中的this?將指向全局的?window?對象;反之,因為作為ECMA 5,內部函數中的功能this?是未定義的。)

4.封裝JavaScript源文件的全部內容到一個函數塊有什么意義及理由?

這是一個越來越普遍的做法,被許多流行的JavaScript庫(jQuery,Node.js等)采用。這種技術創建了一個圍繞文件全部內容的閉包,也許是最重要的是,創建了一個私有的命名空間,從而有助于避免不同JavaScript模塊和庫之間潛在的名稱沖突。

這種技術的另一個特點是,允許一個易于引用的(假設更短的)別名用于全局變量。這通常用于,例如,jQuery插件中。jQuery允許你使用jQuery.noConflict(),來禁用?$?引用到jQuery命名空間。在完成這項工作之后,你的代碼仍然可以使用$?利用這種閉包技術,如下所示:

(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

5.在JavaScript源文件的開頭包含?use strict?有什么意義和好處?

對于這個問題,既簡要又最重要的答案是,use strict?是一種在JavaScript代碼運行時自動實行更嚴格解析和錯誤處理的方法。那些被忽略或默默失敗了的代碼錯誤,會產生錯誤或拋出異常。通常而言,這是一個很好的做法。

嚴格模式的一些主要優點包括:

  • 使調試更加容易。那些被忽略或默默失敗了的代碼錯誤,會產生錯誤或拋出異常,因此盡早提醒你代碼中的問題,你才能更快地指引到它們的源代碼。
  • 防止意外的全局變量。如果沒有嚴格模式,將值分配給一個未聲明的變量會自動創建該名稱的全局變量。這是JavaScript中最常見的錯誤之一。在嚴格模式下,這樣做的話會拋出錯誤。
  • 消除?this?強制。如果沒有嚴格模式,引用null或未定義的值到?this?值會自動強制到全局變量。這可能會導致許多令人頭痛的問題和讓人恨不得拔自己頭發的bug。在嚴格模式下,引用 null或未定義的?this?值會拋出錯誤。
  • 不允許重復的屬性名稱或參數值。當檢測到對象(例如,var object = {foo: "bar", foo: "baz"};)中重復命名的屬性,或檢測到函數中(例如,function foo(val1, val2, val1){})重復命名的參數時,嚴格模式會拋出錯誤,因此捕捉幾乎可以肯定是代碼中的bug可以避免浪費大量的跟蹤時間。
  • 使eval()?更安全。在嚴格模式和非嚴格模式下,eval()?的行為方式有所不同。最顯而易見的是,在嚴格模式下,變量和聲明在?eval()?語句內部的函數不會在包含范圍內創建(它們會在非嚴格模式下的包含范圍中被創建,這也是一個常見的問題源)。
  • 在?delete使用無效時拋出錯誤。delete操作符(用于從對象中刪除屬性)不能用在對象不可配置的屬性上。當試圖刪除一個不可配置的屬性時,非嚴格代碼將默默地失敗,而嚴格模式將在這樣的情況下拋出異常。

6.考慮以下兩個函數。它們會返回相同的東西嗎? 為什么相同或為什么不相同?

function foo1()
{
  return {
      bar: "hello"
  };
}

function foo2()
{
  return
  {
      bar: "hello"
  };
}

出人意料的是,這兩個函數返回的內容并不相同。更確切地說是:

console.log("foo1 returns:");
console.log(foo1());
console.log("foo2 returns:");
console.log(foo2());

將產生:

foo1 returns:
Object {bar: "hello"}
foo2 returns:
undefined

這不僅是令人驚訝,而且特別讓人困惑的是,?foo2()返回undefined卻沒有任何錯誤拋出。

原因與這樣一個事實有關,即分號在JavaScript中是一個可選項(盡管省略它們通常是非常糟糕的形式)。其結果就是,當碰到?foo2()中包含?return語句的代碼行(代碼行上沒有其他任何代碼),分號會立即自動插入到返回語句之后。

也不會拋出錯誤,因為代碼的其余部分是完全有效的,即使它沒有得到調用或做任何事情(相當于它就是是一個未使用的代碼塊,定義了等同于字符串?"hello"的屬性?bar)。

這種行為也支持放置左括號于JavaScript代碼行的末尾,而不是新代碼行開頭的約定。正如這里所示,這不僅僅只是JavaScript中的一個風格偏好。

7.?NaN?是什么?它的類型是什么?你如何可靠地測試一個值是否等于?NaN??

NaN?屬性代表一個“不是數字”的值。這個特殊的值是因為運算不能執行而導致的,不能執行的原因要么是因為其中的運算對象之一非數字(例如,?"abc" / 4),要么是因為運算的結果非數字(例如,除數為零)。

雖然這看上去很簡單,但?NaN?有一些令人驚訝的特點,如果你不知道它們的話,可能會導致令人頭痛的bug。

首先,雖然?NaN?意味著“不是數字”,但是它的類型,不管你信不信,是?Number

console.log(typeof NaN === "number");  // logs "true"

此外,?NaN?和任何東西比較——甚至是它自己本身!——結果是false:

console.log(NaN === NaN);  // logs "false"

一種半可靠的方法來測試一個數字是否等于 NaN,是使用內置函數?isNaN(),但即使使用?isNaN()?依然并非是一個完美的解決方案。

一個更好的解決辦法是使用?value !== value,如果值等于NaN,只會產生true。另外,ES6提供了一個新的?Number.isNaN()?函數,這是一個不同的函數,并且比老的全局?isNaN()?函數更可靠。

8.下列代碼將輸出什么?并解釋原因。

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);

一個稍微有點編程基礎的回答是:“你不能確定。可能會輸出“0.3”和“true”,也可能不會。JavaScript中的數字和浮點精度的處理相同,因此,可能不會總是產生預期的結果。“

以上所提供的例子就是一個演示了這個問題的典型例子。但出人意料的是,它會輸出:

0.30000000000000004
false

9.討論寫函數?isInteger(x)?的可能方法,用于確定x是否是整數。

這可能聽起來是小菜一碟,但事實上,這很瑣碎,因為ECMAScript 6引入了一個新的正以此為目的?Number.isInteger()?函數。然而,之前的ECMAScript 6,會更復雜一點,因為沒有提供類似的?Number.isInteger()?方法。

問題是,在ECMAScript規格說明中,整數只概念上存在:即,數字值總是存儲為浮點值。

考慮到這一點,最簡單又最干凈的ECMAScript6之前的解決方法(同時也非常穩健地返回?false?,即使一個非數字的值,如字符串或?null?,被傳遞給函數)如下:

function isInteger(x) { return (x^0) === x; }

下面的解決方法也是可行的,雖然不如上面那個方法優雅:

function isInteger(x) { return Math.round(x) === x; }

請注意?Math.ceil()?和?Math.floor()?在上面的實現中等同于?Math.round()

或:

function isInteger(x) { return (typeof x === 'number') && (x % 1 === 0);

相當普遍的一個不正確的解決方案是:

function isInteger(x) { return parseInt(x, 10) === x; }

雖然這個以?parseInt函數為基礎的方法在?x?取許多值時都能工作良好,但一旦?x?取值相當大的時候,就會無法正常工作。問題在于?parseInt()?在解析數字之前強制其第一個參數到字符串。因此,一旦數目變得足夠大,它的字符串就會表達為指數形式(例如,?1e+21)。因此,parseInt()?函數就會去解析?1e+21,但當到達?e字符串的時候,就會停止解析,因此只會返回值?1。注意:

> String(1000000000000000000000)
'1e+21'

> parseInt(1000000000000000000000, 10)
1

> parseInt(1000000000000000000000, 10) === 1000000000000000000000
false

10.下列代碼行1-4如何排序,使之能夠在執行代碼時輸出到控制臺? 為什么?

(function() {
    console.log(1); 
    setTimeout(function(){console.log(2)}, 1000); 
    setTimeout(function(){console.log(3)}, 0); 
    console.log(4);
})();

序號如下:

1
4
3
2

讓我們先來解釋比較明顯而易見的那部分:

  • 1?和?4之所以放在前面,是因為它們是通過簡單調用?console.log()?而沒有任何延遲輸出的
  • 2?之所以放在?3的后面,是因為?2?是延遲了1000毫秒(即,1秒)之后輸出的,而?3?是延遲了0毫秒之后輸出的。

好的。但是,既然?3?是0毫秒延遲之后輸出的,那么是否意味著它是立即輸出的呢?如果是的話,那么它是不是應該在?4?之前輸出,既然?4?是在第二行輸出的?

要回答這個問題,你需要正確理解JavaScript的事件和時間設置。

瀏覽器有一個事件循環,會檢查事件隊列和處理未完成的事件。例如,如果時間發生在后臺(例如,腳本的?onload?事件)時,瀏覽器正忙(例如,處理一個?onclick),那么事件會添加到隊列中。當onclick處理程序完成后,檢查隊列,然后處理該事件(例如,執行?onload?腳本)。

同樣的,?setTimeout()?也會把其引用的函數的執行放到事件隊列中,如果瀏覽器正忙的話。

setTimeout()的第二個參數為0的時候,它的意思是“盡快”執行指定的函數。具體而言,函數的執行會放置在事件隊列的下一個計時器開始。但是請注意,這不是立即執行:函數不會被執行除非下一個計時器開始。這就是為什么在上述的例子中,調用?console.log(4)?發生在調用?console.log(3)?之前(因為調用?console.log(3)?是通過setTimeout被調用的,因此會稍微延遲)。

11.寫一個簡單的函數(少于80個字符),要求返回一個布爾值指明字符串是否為回文結構。

下面這個函數在?str?是回文結構的時候返回true,否則,返回false。

function isPalindrome(str) {
    str = str.replace(/\W/g, '').toLowerCase();
    return (str == str.split('').reverse().join(''));
}

例如:

console.log(isPalindrome("level"));                   // logs 'true'
console.log(isPalindrome("levels"));                  // logs 'false'
console.log(isPalindrome("A car, a man, a maraca"));  // logs 'true'

12.寫一個?sum方法,在使用下面任一語法調用時,都可以正常工作。

console.log(sum(2,3));   // Outputs 5
console.log(sum(2)(3));  // Outputs 5

(至少)有兩種方法可以做到:

方法1

function sum(x) {
  if (arguments.length == 2) {
    return arguments[0] + arguments[1];
  } else {
    return function(y) { return x + y; };
  }
}

在JavaScript中,函數可以提供到?arguments?對象的訪問,arguments?對象提供傳遞到函數的實際參數的訪問。這使我們能夠使用?length?屬性來確定在運行時傳遞給函數的參數數量。

如果傳遞兩個參數,那么只需加在一起,并返回。

否則,我們假設它被以?sum(2)(3)這樣的形式調用,所以我們返回一個匿名函數,這個匿名函數合并了傳遞到?sum()的參數和傳遞給匿名函數的參數。

方法2

function sum(x, y) {
  if (y !== undefined) {
    return x + y;
  } else {
    return function(y) { return x + y; };
  }
}

當調用一個函數的時候,JavaScript不要求參數的數目匹配函數定義中的參數數量。如果傳遞的參數數量大于函數定義中參數數量,那么多余參數將簡單地被忽略。另一方面,如果傳遞的參數數量小于函數定義中的參數數量,那么缺少的參數在函數中被引用時將會給一個?undefined值。所以,在上面的例子中,簡單地檢查第2個參數是否未定義,就可以相應地確定函數被調用以及進行的方式。

13.請看下面的代碼片段:

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

(a)當用戶點擊“Button 4”的時候會輸出什么到控制臺,為什么?(b)提供一個或多個備用的可按預期工作的實現方案。

(a)無論用戶點擊什么按鈕,數字5將總會輸出到控制臺。這是因為,當?onclick?方法被調用(對于任何按鈕)的時候,?for?循環已經結束,變量?i?已經獲得了5的值。(面試者如果能夠談一談有關如何執行上下文,可變對象,激活對象和內部“范圍”屬性貢有助于閉包行為,則可以加分)。

(b)要讓代碼工作的關鍵是,通過傳遞到一個新創建的函數對象,在每次傳遞通過?for?循環時,捕捉到?i?值。下面是三種可能實現的方法:

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', (function(i) {
    return function() { console.log(i); };
  })(i));
  document.body.appendChild(btn);
}

或者,你可以封裝全部調用到在新匿名函數中的?btn.addEventListener?:

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  (function (i) {
    btn.addEventListener('click', function() { console.log(i); });
  })(i);
  document.body.appendChild(btn);
}

也可以調用數組對象的本地?forEach?方法來替代?for?循環:

['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function() { console.log(i); });
  document.body.appendChild(btn);
});

14.下面的代碼將輸出什么到控制臺,為什么?

var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

輸出結果是:

"array 1: length=5 last=j,o,n,e,s"
"array 2: length=5 last=j,o,n,e,s"

arr1?和?arr2?在上述代碼執行之后,兩者相同了,原因是:

  • 調用數組對象的?reverse()?方法并不只返回反順序的陣列,它也反轉了數組本身的順序(即,在這種情況下,指的是?arr1)。
  • ?reverse()?方法返回一個到數組本身的引用(在這種情況下即,arr1)。其結果為,arr2?僅僅是一個到?arr1的引用(而不是副本)。因此,當對?arr2做了任何事情(即當我們調用?arr2.push(arr3);)時,arr1?也會受到影響,因為?arr1?和?arr2?引用的是同一個對象。

這里有幾個側面點有時候會讓你在回答這個問題時,陰溝里翻船:

傳遞數組到另一個數組的?push()?方法會讓整個數組作為單個元素映射到數組的末端。其結果是,語句?arr2.push(arr3);?在其整體中添加?arr3?作為一個單一的元素到?arr2?的末端(也就是說,它并沒有連接兩個數組,連接數組是?concat()?方法的目的)。

和Python一樣,JavaScript標榜數組方法調用中的負數下標,例如?slice()?可作為引用數組末尾元素的方法:例如,-1下標表示數組中的最后一個元素,等等。

15.下面的代碼將輸出什么到控制臺,為什么?

console.log(1 +  "2" + "2");
console.log(1 +  +"2" + "2");
console.log(1 +  -"1" + "2");
console.log(+"1" +  "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);

上面的代碼將輸出以下內容到控制臺:

"122"
"32"
"02"
"112"
"NaN2"
NaN

原因是…

這里的根本問題是,JavaScript(ECMAScript)是一種弱類型語言,它可對值進行自動類型轉換,以適應正在執行的操作。讓我們通過上面的例子來說明這是如何做到的。

例1:1 + "2" + "2"?輸出:"122"?說明:?1 + "2"?是執行的第一個操作。由于其中一個運算對象("2")是字符串,JavaScript會假設它需要執行字符串連接,因此,會將?1?的類型轉換為?"1",?1 + "2"結果就是?"12"。然后,?"12" + "2"?就是?"122"

例2:?1 + +"2" + "2"?輸出:?"32"?說明:根據運算的順序,要執行的第一個運算是?+"2"(第一個?"2"?前面的額外?+?被視為一元運算符)。因此,JavaScript將?"2"?的類型轉換為數字,然后應用一元?+?號(即,將其視為一個正數)。其結果是,接下來的運算就是?1 + 2?,這當然是?3。然后我們需要在一個數字和一個字符串之間進行運算(即,?3?和?"2"),同樣的,JavaScript會將數值類型轉換為字符串,并執行字符串的連接,產生?"32"

例3:?1 + -"1" + "2"?輸出:?"02"??說明:這里的解釋和前一個例子相同,除了此處的一元運算符是?-?而不是?+。先是?"1"?變為?1,然后當應用?-?時又變為了?-1?,然后將其與?1相加,結果為?0,再將其轉換為字符串,連接最后的?"2"?運算對象,得到?"02"

例4:?+"1" + "1" + "2"?輸出:?"112"?說明:雖然第一個運算對象?"1"因為前綴的一元?+?運算符類型轉換為數值,但又立即轉換回字符串,當連接到第二個運算對象?"1"?的時候,然后又和最后的運算對象"2"?連接,產生了字符串?"112"

例5:?"A" - "B" + "2"?輸出:?"NaN2"?說明:由于運算符?-??不能被應用于字符串,并且?"A"?和?"B"?都不能轉換成數值,因此,"A" - "B"的結果是?NaN,然后再和字符串?"2"?連接,得到?"NaN2"?。

例6:?"A" - "B" + 2?輸出:?NaN?說明:參見前一個例子,?"A" - "B"?結果為?NaN。但是,應用任何運算符到NaN與其他任何的數字運算對象,結果仍然是?NaN

16.下面的遞歸代碼在數組列表偏大的情況下會導致堆棧溢出。在保留遞歸模式的基礎上,你怎么解決這個問題?

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        nextListItem();
    }
};

潛在的堆棧溢出可以通過修改nextListItem?函數避免:

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        // process the list item...
        setTimeout( nextListItem, 0);
    }
};

堆棧溢出之所以會被消除,是因為事件循環操縱了遞歸,而不是調用堆棧。當?nextListItem?運行時,如果?item不為空,timeout函數(nextListItem)就會被推到事件隊列,該函數退出,因此就清空調用堆棧。當事件隊列運行其timeout事件,且進行到下一個?item?時,定時器被設置為再次調用?nextListItem。因此,該方法從頭到尾都沒有直接的遞歸調用,所以無論迭代次數的多少,調用堆棧保持清空的狀態。

17.JavaScript中的“閉包”是什么?請舉一個例子。

閉包是一個可以訪問外部(封閉)函數作用域鏈中的變量的內部函數。閉包可以訪問三種范圍中的變量:這三個范圍具體為:(1)自己范圍內的變量,(2)封閉函數范圍內的變量,以及(3)全局變量。

下面是一個簡單的例子:

var globalVar = "xyz";

(function outerFunc(outerArg) {
  var outerVar = 'a';

  (function innerFunc(innerArg) {
    var innerVar = 'b';

    console.log(
      "outerArg = " + outerArg + "\n" +
      "innerArg = " + innerArg + "\n" +
      "outerVar = " + outerVar + "\n" +
      "innerVar = " + innerVar + "\n" +
      "globalVar = " + globalVar);

  })(456);
})(123);

在上面的例子中,來自于?innerFunc,?outerFunc和全局命名空間的變量都在?innerFunc的范圍內。因此,上面的代碼將輸出如下:

outerArg = 123
innerArg = 456
outerVar = a
innerVar = b
globalVar = xyz

18.下面的代碼將輸出什么:

for (var i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i); }, i * 1000 );
}

解釋你的答案。閉包在這里能起什么作用?

上面的代碼不會按預期顯示值0,1,2,3,和4,而是會顯示5,5,5,5,和5。

原因是,在循環中執行的每個函數將整個循環完成之后被執行,因此,將會引用存儲在?i中的最后一個值,那就是5。

閉包可以通過為每次迭代創建一個唯一的范圍,存儲范圍內變量的每個唯一的值,來防止這個問題,如下:

for (var i = 0; i < 5; i++) {
	(function(x) {
    	setTimeout(function() { console.log(x); }, x * 1000 );
    })(i);
}

這就會按預期輸出0,1,2,3,和4到控制臺。

19.以下代碼行將輸出什么到控制臺?

console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));

并解釋。

該代碼將輸出:

0 || 1 = 1
1 || 2 = 1
0 && 1 = 0
1 && 2 = 2

在JavaScript中,?||?和?&&都是邏輯運算符,用于在從左至右計算時,返回第一個可完全確定的“邏輯值”。

或(?||?)運算符。在形如?X||Y的表達式中,首先計算X?并將其解釋執行為一個布爾值。如果這個布爾值true,那么返回true(1),不再計算?Y,因為“或”的條件已經滿足。如果這個布爾值為false,那么我們仍然不能知道?X||Y是真是假,直到我們計算?Y,并且也把它解釋執行為一個布爾值。

因此,?0 || 1?的計算結果為true(1),同理計算1 || 2

與(?&&)運算符。在形如?X&&Y的表達式中,首先計算?X并將其解釋執行為一個布爾值。如果這個布爾值為?false,那么返回?false(0),不再計算?Y,因為“與”的條件已經失敗。如果這個布爾值為true,但是,我們仍然不知道?X&&Y?是真是假,直到我們去計算?Y,并且也把它解釋執行為一個布爾值。

不過,關于?&&運算符有趣的地方在于,當一個表達式計算為“true”的時候,那么就返回表達式本身。這很好,雖然它在邏輯表達式方面計算為“真”,但如果你希望的話也可用于返回該值。這就解釋了為什么,有些令人奇怪的是,?1 && 2返回?2(而不是你以為的可能返回?true?或?1)。

20.執行下面的代碼時將輸出什么?請解釋。

console.log(false == '0')
console.log(false === '0')

代碼將輸出:

true
false

在JavaScript中,有兩種等式運算符。三個等于運算符?===?的作用類似傳統的等于運算符:如果兩側的表達式有著相同的類型和相同的值,那么計算結果為true。而雙等于運算符,會只強制比較它們的值。因此,總體上而言,使用?===而不是?==的做法更好。?!==vs?!=亦是同理。

21.以下代碼將輸出什么?并解釋你的答案。

var a={},
    b={key:'b'},
    c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);

這段代碼將輸出?456(而不是?123)。

原因為:當設置對象屬性時,JavaScript會暗中字符串化參數值。在這種情況下,由于?b?和?c都是對象,因此它們都將被轉換為"[object Object]"。結果就是,?a[b]a[c]均相當于a["[object Object]"]?,并可以互換使用。因此,設置或引用?a[c]和設置或引用?a[b]完全相同。

22.以下代碼行將輸出什么到控制臺?

console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));

并解釋你的答案。

代碼將輸出10!的值(即10!或3628800)。

原因是:

命名函數?f()遞歸地調用本身,當調用?f(1)的時候,只簡單地返回1。下面就是它的調用過程:

f(1): returns n, which is 1
f(2): returns 2 * f(1), which is 2
f(3): returns 3 * f(2), which is 6
f(4): returns 4 * f(3), which is 24
f(5): returns 5 * f(4), which is 120
f(6): returns 6 * f(5), which is 720
f(7): returns 7 * f(6), which is 5040
f(8): returns 8 * f(7), which is 40320
f(9): returns 9 * f(8), which is 362880
f(10): returns 10 * f(9), which is 3628800

23.請看下面的代碼段。控制臺將輸出什么,為什么?

(function(x) {
    return (function(y) {
        console.log(x);
    })(2)
})(1);

控制臺將輸出?1,即使從來沒有在函數內部設置過x的值。原因是:

正如我們在JavaScript招聘指南中解釋過的那樣,閉包是一個函數,連同在閉包創建的時候,其范圍內的所有變量或函數一起。在JavaScript中,閉包是作為一個“內部函數”實施的:即,另一個函數主體內定義的函數。閉包的一個重要特征是,內部函數仍然有權訪問外部函數的變量。

因此,在本例中,由于?x未在函數內部中定義,因此在外部函數范圍中搜索定義的變量?x,且被發現具有1的值。

24.下面的代碼將輸出什么到控制臺,為什么:

var hero = {
    _name: 'John Doe',
    getSecretIdentity: function (){
        return this._name;
    }
};

var stoleSecretIdentity = hero.getSecretIdentity;

console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

代碼有什么問題,以及應該如何修復。

代碼將輸出:

undefined
John Doe

第一個?console.log之所以輸出?undefined,是因為我們正在從?hero對象提取方法,所以調用了全局上下文中(即窗口對象)的?stoleSecretIdentity(),而在此全局上下文中,?_name屬性不存在。

其中一種修復stoleSecretIdentity()?函數的方法如下:

var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);

25.創建一個給定頁面上的一個DOM元素,就會去訪問元素本身及其所有子元素(不只是它的直接子元素)的函數。對于每個被訪問的元素,函數應該傳遞元素到提供的回調函數。

此函數的參數為:

  • DOM元素
  • 回調函數(將DOM元素作為其參數)

訪問樹(DOM)的所有元素是經典的深度優先搜索算法應用。下面是一個示范的解決方案:

function Traverse(p_element,p_callback) {
   p_callback(p_element);
   var list = p_element.children;
   for (var i = 0; i < list.length; i++) {
       Traverse(list[i],p_callback);  // recursive call
   }
}

譯文鏈接:http://www.ztsusc.tw/article/25-essential-javascript-interview-questions.html
英文原文:25 Essential JavaScript Interview Questions
翻譯作者:碼農網 – 小峰
轉載必須在正文中標注并保留原文鏈接、譯文鏈接和譯者等信息。]

發表我的評論

取消評論
表情 插代碼

Hi,您需要填寫昵稱和郵箱!

  • 必填項
  • 必填項
22选5今晚开奖公告