問題1:閉包
考慮下面的代碼:
1
2
3
4
5
6
|
var nodes = document.getElementsByTagName( 'button' );
for ( var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener( 'click' , function () {
console.log( 'You clicked element #' + i);
});
} |
請問,如果用戶點擊第一個和第四個按鈕,控制檯上會輸出什麼?爲什麼?
答案
上面代碼的目的在於檢測JavaScript的一個重要概念:閉包。對於每一個JavaScript開發者來說,如果你想在網頁中編寫5行以上的代碼,那麼準確理解和恰當使用閉包是非常重要的。控制檯會輸出兩次You clicked element #NODES_LENGTH,其中#NODES_LENGTH等於nodes內的元素個數。當for循環結束時,變量i的值等於nodes的長度。另外,由於i是在事件被添加時的函數作用域,因此變量i屬於事件的閉包。由於閉包中變量的值不是靜態的,因而i的值並不是事件被添加時所賦予的值(比如添加第一個按鈕時i爲0,第二個按鈕時i爲1)。當事件被執行時,控制檯會輸出變量i當前的值,即i等於nodes的長度。
問題2:閉包
修復上題的問題,使得點擊第一個按鈕時輸出1,點擊第二個按鈕時輸出2。
答案
有多種辦法解決這個問題,下面我給出其中的兩種。
第一個解決方案要用到一個IIFE來創建另外一個閉包,從而得到所希望的i的值。相應的代碼如下:
1
2
3
4
5
6
7
8
|
var nodes = document.getElementsByTagName( 'button' );
for ( var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener( 'click' , ( function (i) {
return function () {
console.log( 'You clicked element #' + i);
}
})(i));
} |
另一個解決方案不使用IIFE,而是將函數移到循環的外面,代碼如下:
1
2
3
4
5
6
7
8
9
10
|
function handlerWrapper(i) {
return function () {
console.log( 'You clicked element #' + i);
}
} var nodes = document.getElementsByTagName( 'button' );
for ( var i = 0; i < nodes.length; i++) {
nodes[i].addEventListener( 'click' , handlerWrapper(i));
} |
問題3:數據類型
考慮如下代碼:
1
2
3
4
|
console.log( typeof null );
console.log( typeof {});
console.log( typeof []);
console.log( typeof undefined);
|
答案
這一個問題看起來似乎有點傻,但是它測試了typeof 操作符的知識。很多JavaScript開發者並沒有意識到typeof的獨特性。在本例中,控制檯會輸出下面的內容:
object object object undefined
最讓人吃驚的輸出結果可能是第三個,許多開發者認爲typeof [ ] 會返回Array。如果想測試變量值是否爲數組,可以寫下面的代碼:
1
2
3
4
|
var myArray = [];
if (myArray instanceof Array) {
// do something...
} |
問題4:事件循環
下面代碼運行結果是什麼?請解釋。
1
2
3
4
5
6
7
|
function printing() {
console.log(1);
setTimeout( function () { console.log(2); }, 1000);
setTimeout( function () { console.log(3); }, 0);
console.log(4);
} printing(); |
答案
輸出結果:
1 4 3 2
要弄懂數字爲何以這種順序輸出,你需要弄明白setTimeout()是幹什麼的,以及瀏覽器的事件循環工作原理。瀏覽器有一個事件循環用於檢查事件隊列,處理延遲的事件。UI事件(例如,點擊,滾動等),Ajax回調,以及提供給setTimeout()和setInterval()的回調都會依次被事件循環處理。因此,當調用setTimeout()函數時,即使延遲的時間被設置爲0,提供的回調也會被排隊。回調會呆在隊列中,直到指定的時間用完後,引擎開始執行動作(如果它在當前不執行其他的動作)。因此,即使setTimeout()回調被延遲0毫秒,它仍然會被排隊,並且直到函數中其他非延遲的語句被執行完了之後,纔會執行。
有了這些認識,理解輸出結果爲“4”就容易了,因爲它是函數的第一句並且沒有使用setTimeout()函數來延遲。接着輸出“4”,因爲它是沒有被延遲的數字,也沒有進行排隊。然後,剩下了“2”,“3”,兩者都被排隊,但是前者需要等待一秒,後者等待0秒(這意味着引擎完成前兩個輸出之後馬上進行)。這就解釋了爲什麼“3”在“2”之前。
問題5:算法
寫一個判斷質數的isPrime()函數,當其爲質數時返回true,否則返回false。
答案
我認爲這是在面試中最常問到的一個問題。儘管這個問題反覆出現並且也很簡單,但是從候選人提供的答案中能很好地看出候選人的數學和算法能力水平。
首先, 因爲JavaScript不同於C或者Java,因此你不能信任傳遞來的數據類型。如果面試官沒有明確地告訴你,你應該詢問他是否需要做輸入檢查,還是不進行檢查直接寫函數。嚴格上說,應該對函數的輸入進行檢查。
需要記住的第二點,負數不是質數。同樣的,1和0都不是,因此,要對這些數字做檢測。另外,2是唯一的既是偶數又是質數的數字。沒有必要用一個循環來驗證4,6,8。再者,如果一個數字不能被2整除,它同樣也不能被4,6,8等整除,因此你的循環需要跳過這些數字。可以採取其他一些更明智的優化手段,我這裏採用的是適用於大多數情況的。例如,如果一個數字不能被5整除,它也不會被5的倍數整除。所以,沒有必要檢測10,15,20等等。如果你深入瞭解這個問題的解決方案,我建議你去看相關的Wikipedia介紹。
最後一點,你不需要檢查比輸入數字的開方還要大的數字。我感覺人們會遺漏掉這一點,並且也不會因爲此而獲得消極的反饋。但是,展示出這一方面的知識會給你額外加分。
現在你具備了這個問題的背景知識,下面是總結以上所有考慮的解決方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
function isPrime(number) {
// If your browser doesn't support the method Number.isInteger of ECMAScript 6,
// you can implement your own pretty easily
if ( typeof number !== 'number' || !Number.isInteger(number)) {
// Alternatively you can throw an error.
return false ;
}
if (number < 2) {
return false ;
}
if (number === 2) {
return true ;
} else if (number % 2 === 0) {
return false ;
}
var squareRoot = Math.sqrt(number);
for ( var i = 3; i <= squareRoot; i += 2) {
if (number % i === 0) {
return false ;
}
}
return true ;
}
|