37個JavaScript基本面試問題和解答
英文:Toptal 來源:眾成翻譯/xiaosheng222
www.zcfy.cc/article/37-essential-javascript-interview-questions-and-answers
1、使用typeof bar ===“object”來確定bar是否是一個物件時有什麼潛在的缺陷?這個陷阱如何避免?
儘管typeof bar ===“object”是檢查bar是否是物件的可靠方法,但JavaScript中令人驚訝的問題null也被認為是一個物件!
因此,對於大多數開發人員來說,下面的程式碼會將true(而不是false)列印到控制檯:
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使得陣列的情況非常簡單,包括它自己的空檢查:
console.log(Array.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在上面的示例中都未定義。
但是,情況並非如此。這裡的問題是大多數開發人員錯誤地理解語句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,一個重要的例子!)
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。
但在內部函式中,這不再指向myObject。因此,this.foo在內部函式中是未定義的,而對區域性變數self的引用仍然在範圍內並且可以在那裡訪問。
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中最常見的錯誤之一。在嚴格模式下,嘗試這樣做會引發錯誤。
-
消除隱藏威脅。在沒有嚴格模式的情況下,對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()語句內部宣告的變數和函式不會在包含範圍中建立(它們是以非嚴格模式在包含範圍中建立的,這也可能是問題的常見來源)。
-
丟擲無效的使用錯誤的刪除符。 刪除操作符(用於從物件中刪除屬性)不能用於物件的不可配置屬性。當試圖刪除一個不可配置的屬性時,非嚴格程式碼將自動失敗,而在這種情況下,嚴格模式會引發錯誤。
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()返回未定義而沒有引發任何錯誤。
原因與JavaScript中分號在技術上是可選的事實有關(儘管忽略它們通常是非常糟糕的形式)。因此,在foo2()中遇到包含return語句的行(沒有其他內容)時,會在return語句之後立即自動插入分號。
由於程式碼的其餘部分是完全有效的,即使它沒有被呼叫或做任何事情(它只是一個未使用的程式碼塊,它定義了一個屬性欄,它等於字串“hello”),所以不會丟擲任何錯誤。
這種行為也被認為是遵循了在JavaScript中將一行開頭大括號放在行尾的約定,而不是在新行的開頭。如此處所示,這不僅僅是JavaScript中的一種風格偏好。
7、什麼是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()函式不同,也更加可靠。
8、下面的程式碼輸出什麼?解釋你的答案。
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
9、執行下面的程式碼時,按什麼順序將數字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呼叫的,所以稍微延遲了一點)。
10、編寫一個簡單的函式(少於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'
11、寫一個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不需要引數的數量來匹配函式定義中引數的數量。如果傳遞的引數數量超過了函式定義中引數的數量,則超出的引數將被忽略。另一方面,如果傳遞的引數數量少於函式定義中的引數數量,則在函式內引用時,缺少的引數將具有未定義的值。因此,在上面的例子中,通過簡單地檢查第二個引數是否未定義,我們可以確定函式被呼叫的方式並相應地繼續。
12、考慮下面的程式碼片段
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);
}
13、假設d是範圍內的“空”物件:
var d = {};
使用下面的程式碼完成了什麼?
[ 'zebra', 'horse' ].forEach(function(k) {
d[k] = undefined;
});
上面顯示的程式碼片段在物件d上設定了兩個屬性。理想情況下,對具有未設定鍵的JavaScript物件執行的查詢評估為未定義。但是執行這段程式碼會將這些屬性標記為物件的“自己的屬性”。
這是確保物件具有一組給定屬性的有用策略。將該物件傳遞給Object.keys將返回一個包含這些設定鍵的陣列(即使它們的值未定義)。
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是相同的(即['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表示陣列中的最後一個元素,依此類推。
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”轉換為數值, “ - ”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不為null,則將超時函式(nextListItem)推送到事件佇列,並且函式退出,從而使呼叫堆疊清零。當事件佇列執行超時事件時,將處理下一個專案,並設定一個計時器以再次呼叫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。
這是因為迴圈內執行的每個函式將在整個迴圈完成後執行,因此所有函式都會引用儲存在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 );
}
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(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!=也是如此。
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]都等價於[“[object Object]”],並且可以互換使用。因此,設定或引用[c]與設定或引用[b]完全相同。
22、以下程式碼將輸出到控制檯中.
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。
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列印未定義,因為我們從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
}
}
27、在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。
因此輸出將如上所述。
28、考慮下面的程式碼。輸出是什麼,為什麼?
(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);
})();
29、這段程式碼的輸出是什麼?
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
31、此程式碼將列印什麼?
for (let i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
它會列印0 1 2 3 4,因為我們在這裡使用let而不是var。變數i只能在for迴圈的塊範圍中看到。
32、以下幾行輸出什麼,為什麼?
console.log(1 < 2 < 3);
console.log(3 > 2 > 1);
第一條語句返回true,如預期的那樣。
第二個返回false是因為引擎如何針對<和>的操作符關聯性工作。它比較從左到右,所以3> 2> 1 JavaScript翻譯為true> 1. true具有值1,因此它比較1> 1,這是錯誤的。
33、如何在陣列的開頭新增元素?最後如何新增一個?
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'];
34、想象一下你有這樣的程式碼:
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]
35、typeof undefined == typeof NULL的值是什麼?
該表示式將被評估為true,因為NULL將被視為任何其他未定義的變數。
注意:JavaScript區分大小寫,我們在這裡使用NULL而不是null。
36、程式碼返回後會怎麼樣?
console.log(typeof typeof 1);
列印結果:string
typeof 1將返回“number”,typeof“number”將返回字串。
37、以下程式碼輸出什麼?為什麼?
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”候選人。最後,招聘仍然是一門藝術,一門科學 - 還有很多工作要做。