37個JavaScript基本面試問題和解答

一、使用typeof bar ===「object」來肯定bar是不是一個對象時有什麼潛在的缺陷?這個陷阱如何避免?

儘管typeof bar ===「object」是檢查bar是不是對象的可靠方法,但JavaScript中使人驚訝的問題是_null_也被認爲是一個對象!javascript

所以,下面的代碼打印到控制檯的是true而不是false:java

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

只要知道這一點,就能夠經過檢查bar是否爲空來輕鬆避免該問題:面試

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

爲了在咱們的答案更加的完整,還有兩件事值得注意:算法

首先,若是bar是一個函數,上面的解決方案將返回false。在大多數狀況下,這是所指望的行爲,可是在您但願函數返回true的狀況下,您能夠將上述解決方案修改成:數組

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

其次,若是bar是數組,則上述解決方案將返回true(例如,若是var bar = [];)。在大多數狀況下,這是所但願的行爲,由於數組確實是對象,可是在您想要對數組也是false的狀況下,能夠將上述解決方案修改成:瀏覽器

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

可是,還有一個替代方法對空值,數組和函數返回false,但對於對象則爲true:安全

console.log((bar !== null) && (bar.constructor === Object));

或者,若是您使用jQuery:閉包

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

ES5使得數組的狀況很是簡單,包括它本身的空檢查:app

console.log(Array.isArray(bar));

二、下面的代碼將輸出到控制檯的是什麼,爲何?

(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在上面的示例中都未定義。ide

可是,狀況並不是如此。這裏的問題是大多數開發人員錯誤地理解語句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關鍵字後面),所以它仍然在做用域內,即便在封閉函數以外。

注意,在嚴格模式下(即,使用strict),語句var a = b = 3;會產生一個ReferenceError的運行時錯誤:b沒有定義,從而避免了可能致使的任何頭headfakes/bugs。 (這就是爲何你應該在你的代碼中使用strict,一個重要的例子!)

三、下面的代碼將輸出到控制檯的是什麼?,爲何?

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。

但在內部函數中,這再也不指向myObject。所以,this.foo在內部函數中是未定義的,而對局部變量self的引用仍然在範圍內而且能夠在那裏訪問。

四、在功能塊中封裝JavaScript源文件的所有內容的重要性和緣由是什麼?

這是一種日益廣泛的作法,被許多流行的JavaScript庫(jQuery,Node.js等)所採用。這種技術在文件的所有內容周圍建立一個閉包,這可能最重要的是建立一個私有名稱空間,從而有助於避免不一樣JavaScript模塊和庫之間的潛在名稱衝突。

這種技術的另外一個特色是爲全局變量提供一個容易引用(可能更短)的別名。例如,這一般用於jQuery插件。 jQuery容許您使用jQuery.noConflict()來禁用對jQuery名稱空間的$引用。若是這樣作了,你的代碼仍然可使用$使用閉包技術,以下所示:

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

五、在JavaScript源文件的開頭包含'use strict'的意義和有什麼好處?

這裏最簡單也是最重要的答案是use strict是_一種在運行時自動執行更嚴格的JavaScript代碼解析和錯誤處理的方法_。若是代碼錯誤被忽略或失敗,將會產生錯誤或拋出異常。總的來講,這是一個很好的作法。

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

  • 使調試更容易。 若是代碼錯誤原本會被忽略或失敗,那麼如今將會產生錯誤或拋出異常,從而更快地發現代碼中的問題,並更快地指引它們的源代碼。
  • 防止意外全局。 若是沒有嚴格模式,將值賦給未聲明的變量會自動建立一個具備該名稱的全局變量。這是JavaScript中最多見的錯誤之一。在嚴格模式下,嘗試這樣作會引起錯誤。
  • 消除隱藏威脅。在沒有嚴格模式的狀況下,對null或undefined的這個值的引用會自動強制到全局。這可能會致使許多_headfakes_和_pull-out-your-hair_類型的錯誤。在嚴格模式下,引用null或undefined的這個值會引起錯誤。
  • 不容許重複的參數值。 嚴格模式在檢測到函數的重複命名參數(例如,函數foo(val1,val2,val1){})時會引起錯誤,從而捕獲代碼中幾乎能夠確定存在的錯誤,不然您可能會浪費大量的時間追蹤。

    • 注意:它曾經是(在ECMAScript 5中)strict模式將禁止重複的屬性名稱(例如var object = {foo:「bar」,foo:「baz」};)可是從ECMAScript 2015 開始,就再也不有這種狀況了。
  • 使eval()更安全。 eval()在嚴格模式和非嚴格模式下的行爲方式有些不一樣。最重要的是,在嚴格模式下,在eval()語句內部聲明的變量和函數不會在包含範圍中建立(它們是以非嚴格模式在包含範圍中建立的,這也多是問題的常見來源)。
  • 拋出無效的使用錯誤的刪除符。 刪除操做符(用於從對象中刪除屬性)不能用於對象的不可配置屬性。當試圖刪除一個不可配置的屬性時,非嚴格代碼將自動失敗,而在這種狀況下,嚴格模式會引起錯誤。

六、考慮下面的兩個函數。他們都會返回一樣的值嗎?爲何或者爲何不?

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()返回未定義而沒有引起任何錯誤。

緣由與JavaScript中分號在技術上是可選的事實有關(儘管忽略它們一般是很是糟糕的形式)。所以,在foo2()中遇到包含return語句的行(沒有其餘內容)時,_會在return語句以後當即自動插入分號。_

因爲代碼的其他部分是徹底有效的,即便它沒有被調用或作任何事情(它只是一個未使用的代碼塊,它定義了一個屬性欄,它等於字符串「hello」),因此不會拋出任何錯誤。

這種行爲也被認爲是遵循了在JavaScript中將一行開頭大括號放在行尾的約定,而不是在新行的開頭。如此處所示,這不只僅是JavaScript中的一種風格偏好。

七、什麼是NaN?它的類型是什麼?如何可靠地測試一個值是否等於NaN?

NaN屬性表示「不是數字」的值。這個特殊值是因爲一個操做數是非數字的(例如「abc」/ 4)或者由於操做的結果是非數字而沒法執行的。

雖然這看起來很簡單,但NaN有一些使人驚訝的特徵,若是人們沒有意識到這些特徵,就會致使bug。

一方面,雖然NaN的意思是「不是數字」,但它的類型是,數字:

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

此外,NaN相比任何事情 - 甚至自己! - 是false:

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

測試數字是否等於NaN的半可靠方法是使用內置函數isNaN(),但即便使用isNaN()也不是一個好的解決方案。.

一個更好的解決方案要麼是使用value!==值,若是該值等於NaN,那麼只會生成true。另外,ES6提供了一個新的Number.isNaN()函數 ,它與舊的全局isNaN()函數不一樣,也更加可靠。

八、下面的代碼輸出什麼?解釋你的答案。

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

對這個問題的一個有教養的回答是:「你不能肯定。它可能打印出0.3和true,或者可能不打印。 JavaScript中的數字所有用浮點精度處理,所以可能不會老是產生預期的結果。「

上面提供的示例是演示此問題的經典案例。使人驚訝的是,它會打印出來:

0.30000000000000004
false

一個典型的解決方案是比較兩個數字與特殊常數Number.EPSILON之間的絕對差值:

function areTheNumbersAlmostEqual(num1, num2) {
    return Math.abs( num1 - num2 ) < Number.EPSILON;
}
console.log(areTheNumbersAlmostEqual(0.1 + 0.2, 0.3));

討論寫函數的可能方法isInteger(x),它肯定x是不是一個整數。

這聽起來很平凡,事實上,ECMAscript 6爲此正好引入了一個新的Number.isInteger()函數,這是微不足道的。可是,在ECMAScript 6以前,這有點複雜,由於沒有提供與Number.isInteger()方法等價的方法。

問題在於,在ECMAScript規範中,整數只在概念上存在;即數值始終做爲浮點值存儲。

考慮到這一點,最簡單,最清潔的ECMAScript-6以前的解決方案(即便將非數字值(例如字符串或空值)傳遞給該函數,該解決方案也具備足夠的可靠性以返回false)將成爲如下用法按位異或運算符:

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

九、執行下面的代碼時,按什麼順序將數字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()而沒有任何延遲記錄的
  • 在3以後顯示,由於在延遲1000毫秒(即1秒)以後記錄2,而在0毫秒的延遲以後記錄3。

好的。可是,若是在延遲0毫秒後記錄3,這是否意味着它正在被當即記錄?並且,若是是這樣,不該該在4以前記錄它,由於4是由後面的代碼行記錄的嗎?

答案與正確理解JavaScript事件和時間有關。 .

瀏覽器有一個事件循環,它檢查事件隊列並處理未決事件。例如,若是在瀏覽器繁忙時(例如,處理onclick)在後臺發生事件(例如腳本onload事件),則該事件被附加到隊列中。當onclick處理程序完成時,將檢查隊列並處理該事件(例如,執行onload腳本)。

一樣,若是瀏覽器繁忙,setTimeout()也會將其引用函數的執行放入事件隊列中。

當值爲零做爲setTimeout()的第二個參數傳遞時,它將嘗試「儘快」執行指定的函數。具體來講,函數的執行放置在事件隊列中,以在下一個計時器滴答時發生。但請注意,這不是直接的;該功能不會執行,直到下一個滴答聲。這就是爲何在上面的例子中,調用console.log(4)發生在調用console.log(3)以前(由於調用console.log(3)是經過setTimeout調用的,因此稍微延遲了一點)。

十、編寫一個簡單的函數(少於160個字符),返回一個布爾值,指示字符串是不是palindrome

若是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'

十一、寫一個sum方法,當使用下面的語法調用時它將正常工做。

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

有(至少)兩種方法能夠作到這一點:

METHOD 1

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

在JavaScript中,函數提供對參數對象的訪問,該對象提供對傳遞給函數的實際參數的訪問。這使咱們可以使用length屬性在運行時肯定傳遞給函數的參數的數量

若是傳遞兩個參數,咱們只需將它們相加並返回。

不然,咱們假設它是以sum(2)(3)的形式被調用的,因此咱們返回一個匿名函數,它將傳遞給sum()(在本例中爲2)的參數和傳遞給匿名函數的參數這種狀況3)。

METHOD 2

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

當函數被調用時,JavaScript不須要參數的數量來匹配函數定義中參數的數量。若是傳遞的參數數量超過了函數定義中參數的數量,則超出的參數將被忽略。另外一方面,若是傳遞的參數數量少於函數定義中的參數數量,則在函數內引用時,缺乏的參數將具備未定義的值。所以,在上面的例子中,經過簡單地檢查第二個參數是否未定義,咱們能夠肯定函數被調用的方式並相應地繼續。

十二、考慮下面的代碼片斷

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) 當用戶點擊「按鈕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);
});

最後,最簡單的解決方案,若是你在ES6 / ES2015上下文中,就是使用let i而不是var i:

for (let 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);
}

1三、假設d是範圍內的「空」對象:

var d = {};

...使用下面的代碼完成了什麼?

[ 'zebra', 'horse' ].forEach(function(k) {
    d[k] = undefined;
});

上面顯示的代碼片斷在對象d上設置了兩個屬性。理想狀況下,對具備未設置鍵的JavaScript對象執行的查找評估爲未定義。可是運行這段代碼會將這些屬性標記爲對象的「本身的屬性」。

這是確保對象具備一組給定屬性的有用策略。將該對象傳遞給Object.keys將返回一個包含這些設置鍵的數組(即便它們的值未定義)。

1四、下面的代碼將輸出到控制檯,爲何?

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是相同的(即['n','h','o','j',['j','o','n','e','s']])上述代碼因爲如下緣由而被執行:

  • 調用數組對象的reverse()方法不只以相反的順序返回數組,它還顛倒了數組自己的順序(即在這種狀況下,arr1)。
  • reverse()方法返回對數組自己的引用(即,在這種狀況下爲arr1)。所以,arr2僅僅是對arr1的引用(而不是副本)。所以,當對arr2作任何事情時(即,當咱們調用arr2.push(arr3);)時,arr1也會受到影響,由於arr1和arr2只是對同一個對象的引用。

這裏有幾個觀點可讓人們回答這個問題:

  • 將數組傳遞給另外一個數組的push()方法會將整個數組做爲單個元素推入數組的末尾。結果,聲明arr2.push(arr3);將arr3做爲一個總體添加到arr2的末尾(即,它不鏈接兩個數組,這就是concat()方法的用途)。
  • 像Python同樣,JavaScript在調用像slice()這樣的數組方法時,會認可負面下標,以此做爲在數組末尾引用元素的方式;例如,下標-1表示數組中的最後一個元素,依此類推。

1五、下面的代碼將輸出到控制檯,爲何?

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」轉換爲數值, 「 - 」B「產生NaN,而後​​與字符串」2「串聯產生」NaN2「。

例6:「A」 - 「B」+2輸出:NaN說明:在前面的例子中,「A」 - 「B」產生NaN。可是任何運算符應用於NaN和其餘數字操做數仍然會產生NaN。

1六、若是數組列表太大,如下遞歸代碼將致使堆棧溢出。你如何解決這個問題,仍然保留遞歸模式?

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不爲null,則將超時函數(nextListItem)推送到事件隊列,而且函數退出,從而使調用堆棧清零。當事件隊列運行超時事件時,將處理下一個項目,並設置一個計時器以再次調用nextListItem。所以,該方法從頭至尾不通過直接遞歸調用便可處理,所以調用堆棧保持清晰,不管迭代次數如何。

1七、什麼是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

1八、如下代碼的輸出是什麼:

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

解釋你的答案。如何在這裏使用閉包?

顯示的代碼示例不會顯示值0,1,2,3和4,這多是預期的;而是顯示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記錄到控制檯的可能結果。

ES2015上下文中,您能夠在原始代碼中簡單地使用let而不是var:

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

1九、如下幾行代碼輸出到控制檯?

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(1),而且不計算Y,由於「或」條件已經知足。可是,若是此布爾值爲「假」,咱們仍然不知道X || Y是真仍是假,直到咱們評估Y,並將其解釋爲布爾值。

所以,0 || 1評估爲真(1),正如1 || 2。

和(&&)運算符。在X && Y形式的表達式中,首先評估X並將其解釋爲布爾值。若是此布爾值爲false,則返回false(0)而且不評估Y,由於「and」條件已失敗。可是,若是這個布爾值爲「真」,咱們仍然不知道X && Y是真仍是假,直到咱們評估Y,並將其解釋爲布爾值。

然而,&&運算符的有趣之處在於,當表達式評估爲「真」時,則返回表達式自己。這很好,由於它在邏輯表達式中被視爲「真」,但也能夠用於在您關心時返回該值。這解釋了爲何,有點使人驚訝的是,1 && 2返回2(而你可能會指望它返回true或1)。

20 、下面的代碼執行時輸出是什麼?說明。

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

該代碼將輸出:

true
false

在JavaScript中,有兩套相等運算符。三重相等運算符===的行爲與任何傳統的相等運算符相同:若是兩側的兩個表達式具備相同的類型和相同的值,則計算結果爲true。然而,雙等號運算符在比較它們以前試圖強制這些值。所以,一般使用===而不是==。對於!== vs!=也是如此。

2一、如下代碼的輸出是什麼?解釋你的答案。

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]都等價於[「[object Object]」],而且能夠互換使用。所以,設置或引用[c]與設置或引用[b]徹底相同。

2二、如下代碼將輸出到控制檯中.

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

該代碼將輸出10階乘的值(即10!或3,628,800)。

緣由以下:

命名函數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。

2四、如下代碼將輸出到控制檯以及爲何

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打印未定義,由於咱們從hero對象中提取方法,因此stoleSecretIdentity()在_name屬性不存在的全局上下文(即窗口對象)中被調用。

修復stoleSecretIdentity()函數的一種方法以下:

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

2五、建立一個函數,給定頁面上的DOM元素,將訪問元素自己及其全部後代(_不只僅是它的直接子元素_)。對於每一個訪問的元素,函數應該將該元素傳遞給提供的回調函數。

該函數的參數應該是:

  • 一個 DOM 元素
  • 一個回調函數(以DOM元素做爲參數)

訪問樹中的全部元素(DOM)是[經典的深度優先搜索算法]Depth-First-Search algorithm應用程序。如下是一個示例解決方案:

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

2七、在JavaScript中測試您的這些知識:如下代碼的輸出是什麼?

var length = 10;
function fn() {
    console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);

輸出:

10
2

爲何不是10和5?

首先,因爲fn做爲函數方法的參數傳遞,函數fn的做用域(this)是窗口。 var length = 10;在窗口級別聲明。它也能夠做爲window.length或length或this.length來訪問(當這個===窗口時)。

方法綁定到Object obj,obj.method用參數fn和1調用。雖然方法只接受一個參數,但調用它時已經傳遞了兩個參數;第一個是函數回調,其餘只是一個數字。

當在內部方法中調用fn()時,該函數在全局級別做爲參數傳遞,this.length將有權訪問在Object obj中定義的var length = 10(全局聲明)而不是length = 5。

如今,咱們知道咱們可使用arguments []數組訪問JavaScript函數中的任意數量的參數。

所以arguments0只不過是調用fn()。在fn裏面,這個函數的做用域成爲參數數組,而且記錄參數[]的長度將返回2。

所以輸出將如上所述。

2八、考慮下面的代碼。輸出是什麼,爲何?

(function () {
    try {
        throw new Error();
    } catch (x) {
        var x = 1, y = 2;
        console.log(x);
    }
    console.log(x);
    console.log(y);
})();
1
undefined
2

var語句被掛起(沒有它們的值初始化)到它所屬的全局或函數做用域的頂部,即便它位於with或catch塊內。可是,錯誤的標識符只在catch塊內部可見。它至關於:

(function () {
    var x, y; // outer and hoisted
    try {
        throw new Error();
    } catch (x /* inner */) {
        x = 1; // inner x, not the outer one
        y = 2; // there is only one y, which is in the outer scope
        console.log(x /* inner */);
    }
    console.log(x);
    console.log(y);
})();

2九、這段代碼的輸出是什麼?

var x = 21;
var girl = function () {
    console.log(x);
    var x = 20;
};
girl ();

21,也不是20,結果是‘undefined’的

這是由於JavaScript初始化沒有被掛起。

(爲何它不顯示21的全局值?緣由是當函數執行時,它檢查是否存在本地x變量但還沒有聲明它,所以它不會查找全局變量。 )

30、你如何克隆一個對象?

var obj = {a: 1 ,b: 2}
var objclone = Object.assign({},obj);

如今objclone的值是{a:1,b:2},但指向與obj不一樣的對象。

但請注意潛在的缺陷:Object.clone()只會執行淺拷貝,而不是深拷貝。這意味着嵌套的對象不會被複制。他們仍然引用與原始相同的嵌套對象:

let obj = {
    a: 1,
    b: 2,
    c: {
        age: 30
    }
};

var objclone = Object.assign({},obj);
console.log('objclone: ', objclone);

obj.c.age = 45;
console.log('After Change - obj: ', obj);           // 45 - This also changes
console.log('After Change - objclone: ', objclone); // 45
for (let i = 0; i < 5; i++) {
  setTimeout(function() { console.log(i); }, i * 1000 );
}

3一、此代碼將打印什麼?

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

它會打印0 1 2 3 4,由於咱們在這裏使用let而不是var。變量i只能在for循環的塊範圍中看到。

3二、如下幾行輸出什麼,爲何?

console.log(1 < 2 < 3);
console.log(3 > 2 > 1);

第一條語句返回true,如預期的那樣。

第二個返回false是由於引擎如何針對<和>的操做符關聯性工做。它比較從左到右,因此3> 2> 1 JavaScript翻譯爲true> 1. true具備值1,所以它比較1> 1,這是錯誤的。

3三、如何在數組的開頭添加元素?最後如何添加一個?

var myArray = ['a', 'b', 'c', 'd'];
myArray.push('end');
myArray.unshift('start');
console.log(myArray); // ["start", "a", "b", "c", "d", "end"]

使用ES6,可使用擴展運算符:

myArray = ['start', ...myArray];
myArray = [...myArray, 'end'];

或者,簡而言之:

myArray = ['start', ...myArray, 'end'];

3四、想象一下你有這樣的代碼:

var a = [1, 2, 3];

a)這會致使崩潰嗎?

a[10] = 99;

b)這個輸出是什麼?

console.log(a[6]);

a)它不會崩潰。 JavaScript引擎將使陣列插槽3至9成爲「空插槽」。

b)在這裏,a [6]將輸出未定義的值,但時隙仍爲空,而不是未定義的。在某些狀況下,這多是一個重要的細微差異。例如,使用map()時,map()的輸出中的空插槽將保持爲空,但未定義的插槽將使用傳遞給它的函數重映射:

var b = [undefined];
b[2] = 1;
console.log(b);             // (3) [undefined, empty × 1, 1]
console.log(b.map(e => 7)); // (3) [7,         empty × 1, 7]

3五、typeof undefined == typeof NULL的值是什麼?

該表達式將被評估爲true,由於NULL將被視爲任何其餘未定義的變量。

注意:JavaScript區分大小寫,咱們在這裏使用NULL而不是null。

3六、代碼返回後會怎麼樣?

console.log(typeof typeof 1);

string

typeof 1將返回「number」,typeof「number」將返回字符串。

3七、如下代碼輸出什麼?爲何?

var b = 1;
function outer(){
       var b = 2
    function inner(){
        b++;
        var b = 3;
        console.log(b)
    }
    inner();
}
outer();

輸出到控制檯將是「3」。

在這個例子中有三個閉包,每一個都有它本身的var b聲明。當調用變量時,將按照從本地到全局的順序檢查閉包,直到找到實例。因爲內部閉包有本身的b變量,這就是輸出。

此外,因爲提高內部的代碼將被解釋以下:

function inner () {
    var b; // b is undefined
    b++; // b is NaN
    b = 3; // b is 3
    console.log(b); // output "3"
}

面試比棘手的技術問題要多,因此這些僅僅是做爲指導。並非每一個值得聘用的「A」候選人都可以回答全部問題,也不會回答他們都保證有「A」候選人。在這一天結束時,招聘仍然是一門藝術,一門科學 - 還有不少工做。.

英文原文地址:https://www.toptal.com/javasc...

相關文章
相關標籤/搜索