題目來自 25 Essential JavaScript Interview Questions。閒來無事,正好切一下。
一
What is a potential pitfall with using typeof bar === "object"
to determine if bar is an object? How can this pitfall be avoided?
老生常談的問題,用 typeof
是否能準確判斷一個物件變數,答案是否定的,null
的結果也是 object,Array
的結果也是 object,有時候我們需要的是 “純粹” 的 object 物件。
如何規避這個問題?
1 2 3 4 5 6 7 |
var obj = {}; // 1 console.log((obj !== null) & (typeof obj === "object") && (toString.call(obj) !== "[object Array]")); // 2 console.log(Object.prototype.toString.call(obj) === "[object Object]"); |
二
What will the code below output to the console and why?
1 2 3 4 5 6 |
(function(){ var a = b = 3; })(); console.log("a defined? " + (typeof a !== 'undefined')); console.log("b defined? " + (typeof b !== 'undefined')); |
這題不難,IIFE 中的賦值過程其實是(賦值過程從右到左):
1 2 3 4 |
(function(){ b = 3; var a = b; })(); |
接下去就不難了,a 是區域性變數,b 是全域性變數。
三
What will the code below output to the console and why?
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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(); |
前面兩個輸出沒有問題,都是 bar,問題出在後面兩個。用了 IIFE 後,匿名函式內的 this 其實已經指到了 window,所以第三個輸出 this.foo 其實是 window.foo,而全域性物件並沒有 foo 這個 key,所以輸出 undefined,而第四個輸出,因為 self 引用了 myObject,所以還是 bar。
四
What is the significance of, and reason for, wrapping the entire content of a JavaScript source file in a function block?
為什麼要用 IIFE?
簡單來說就是為了能模組化,建立私有變數等等,很多類庫(比如 jQuery)都用了這樣的寫法。
可以參考我以前翻譯的一篇文章 詳解javascript立即執行函式表示式(IIFE)
五
What is the significance, and what are the benefits, of including ‘use strict’ at the beginning of a JavaScript source file?
嚴格模式下進行 Javascript 開發有啥好處?
這個就不展開來了,可以參考阮一峰老師的 Javascript 嚴格模式詳解 或者自行谷歌百度。
六
Consider the two functions below. Will they both return the same thing? Why or why not?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function foo1() { return { bar: "hello" }; } function foo2() { return { bar: "hello" }; } |
執行以上兩個函式,會返回相同的東西嗎?
不會,第二個函式會返回 undefined
。這是由於 Javascript 的封號插入機制決定的,如果某行程式碼,return 關鍵詞後沒有任何東西了,將會自動插入一個封號,顯然 foo2 函式中,當 return 後被插入一個封號後,儘管後面的語句不符合規定,但是因為沒有執行到,所以也不會報錯了。沒有 return 任何東西的函式,預設返回 undefined。
所以很多 Javascript 規範建議把 { 寫在一行中,而不是另起一行。
七
What is NaN? What is its type? How can you reliably test if a value is equal to NaN?
NaN 是什麼鬼?typeof 的結果是?如果一個變數的值是 NaN,怎麼確定?
NaN 是 ‘not a number’ 的縮寫,表示 “不是一個數字”,通常會在運算過程中產生:
1 2 |
console.log('abc' / 4); console.log(4 * 'a'); |
雖然它 “不是一個數字”,但是 NaN 的 typeof 結果卻是 number:
1 |
console.log(typeof (4 * 'a')); // number |
NaN 和任何變數都不相等,包括 NaN 自己:
1 |
console.log(NaN === NaN); // false |
判斷一個變數是不是 NaN 可以用 isNaN()
函式,但是這 並不是一個完美的函式,有些時候用 value !== value
似乎更準確,幸運的是,ES6 已經有 Number.isNaN()
方法,將比 isNaN()
準確的多。
八
What will the code below output? Explain your answer.
1 2 |
console.log(0.1 + 0.2); console.log(0.1 + 0.2 == 0.3); |
上面程式碼的輸出結果是什麼?
這個問題正好我之前研究過,有興趣的可以參考下 【0.1 + 0.2 = 0.30000000000000004】該怎樣理解?,看懂了還有興趣的可以看下這篇 玉伯的一道課後題題解(關於 IEEE 754 雙精度浮點型精度損失)
九
Discuss possible ways to write a function isInteger(x)
that determines if x
is an integer.
寫一個方法 isInterger(x),可以用來判斷一個變數是否是整數。
ES6 中自帶了 Number.isInteger()
方法。但是目前 ES5 中沒有自帶的方法,可以把一個數去掉小數點後和原數進行比較,判斷是否相等,那麼問題就演變成如何對一個數進行取整了。
1 2 3 4 5 6 7 8 |
var a = -1.2223; console.log(a ^ 0); // -1 console.log(a | 0); // -1 console.log(a > 0); // -1 console.log(Math.round(a)); // -1 console.log(Math.floor(a)); // -2 console.log(Math.ceil(a)); // -1 |
十
In what order will the numbers 1-4 be logged to the console when the code below is executed? Why?
1 2 3 4 5 6 |
(function() { console.log(1); setTimeout(function(){console.log(2)}, 1000); setTimeout(function(){console.log(3)}, 0); console.log(4); })(); |
以上程式碼的輸出結果是?
這題不難,只要知道 Javascript 是單執行緒的語言, 一些非同步事件是在主體 js 執行完之後執行即可,所以主體的 1、4 先輸出,而後是 3、2,沒有問題,因為 3 的定時設定比 2 早。
具體可以參考我之前的文章 從setTimeout談JavaScript執行機制
十一
Write a simple function (less than 80 characters) that returns a boolean indicating whether or not a string is a palindrome.
判斷一個字串是不是迴文。
1 2 3 4 |
function isPalindrome(str) { str = str.replace(/\W/g, '').toLowerCase(); return (str == str.split('').reverse().join('')); } |
這裡想到一個進階題,求字串最長迴文子串,可以參考 求最長迴文子串 – leetcode 5. Longest Palindromic Substring
十二
Write a sum
method which will work properly when invoked using either syntax below.
1 2 |
console.log(sum(2,3)); // Outputs 5 console.log(sum(2)(3)); // Outputs 5 |
寫一個 sum 方法,使得以上程式碼得到預期結果。這題可以參考我以前的文章 湯姆大叔的6道javascript程式設計題題解 中的最後一題,理論上此題更簡單,因為它沒要求能擴充套件(比如 sum(2)(3)(4)),甚至可以這樣:
1 2 3 4 5 6 7 |
function sum(x) { if (arguments.length == 2) { return arguments[0] + arguments[1]; } else { return function(y) { return x + y; }; } } |
或者這樣:
1 2 3 4 5 6 7 |
function sum(x, y) { if (y !== undefined) { return x + y; } else { return function(y) { return x + y; }; } } |
十三
Consider the following code snippet:
1 2 3 4 5 6 |
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) What gets logged to the console when the user clicks on “Button 4” and why?
(b) Provide one or more alternate implementations that will work as expected.
點選 ‘Button 4’ 後輸出什麼?如何使得輸出能跟預期相同?
答案是輸出 5,事實上點選任意的 button,輸出都是 5。因為迴圈結束後,i 值變成了 5。如何改,使得輸出分別是 0, 1, 2, 3, 4?用閉包在記憶體中儲存變數,可以參考我之前的文章 這10道javascript筆試題你都會麼 中的第 8 題。
十四
What will the code below output to the console and why?
1 2 3 4 5 6 |
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)); |
上面程式碼輸出是?
這道題我答錯了,忽略了 reverse() 方法的一個要重性質,reverse() 方法執行的結果並不是建立一個副本,而是在原陣列上直接操作,並返回該陣列的引用。
知道了這一點,該題也就迎刃而解了。arr2 其實和 arr1 引用了同一個物件,所以在 arr2 上的操作也會同時反映到 arr1 上。
十五
What will the code below output to the console and why ?
1 2 3 4 5 6 |
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); |
以上程式碼輸出什麼?
+”2″ 能將字串 “2” 轉換成整數 2,-“2” 同理,而兩個變數進行 “+” 運算時,如果都是數字和字串,則分別進行數字相加和字串拼接,如果一個是數字一個是字串,則將數字轉為字串,如果是 “-” 運算呢?則將字串轉為數字。
“A” – “B” 會返回 NaN,因為 “A” 和 “B” 無法轉成數字進行運算,這裡不要以為 “A” 和 “B” 能轉為 ASCII碼 進行運算(不要和 C 語言搞混了)。而 NaN 和字串相加,會轉成 “NaN” 和字串去拼接,NaN 和任何數字相加結果還是 NaN。
十六
The following recursive code will cause a stack overflow if the array list is too large. How can you fix this and still retain the recursive pattern?
1 2 3 4 5 6 7 8 9 10 |
var list = readHugeList(); var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... nextListItem(); } }; |
以上程式碼可能會由於遞迴呼叫導致棧溢位,如何規避這個問題?
首先,任何遞迴都可以用迭代來代替,所以改寫成迭代方式肯定沒有問題。
而原文給的解答令人深思:
1 2 3 4 5 6 7 8 9 10 |
var list = readHugeList(); var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... setTimeout( nextListItem, 0); } }; |
利用 setTimeout 的非同步性質,完美地去除了這個呼叫棧。
如果你還是摸不著頭腦,簡單舉個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var list = [0, 1]; var nextListItem = function() { var item = list.pop(); if (item) { nextListItem(); } console.log(item); }; nextListItem(); |
上面的程式碼會依次輸出 0 和 1,因為程式中形成了一個呼叫棧,1 被壓到了棧底,最後出棧。
把程式改成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var list = [0, 1]; var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... setTimeout( nextListItem, 0); } console.log(item); }; nextListItem(); |
這回就是 1 和 0 了,因為 setTimeout 的回撥只有當主體的 js 執行完後才會去執行,所以先輸出了 1,自然也就沒有棧這一說法了。
事實上,並不是所有遞迴都能這樣改寫,如果下一次遞迴呼叫依賴於前一次遞迴呼叫返回的值,就不能這麼改了。
十七
What is a “closure” in JavaScript? Provide an example.
談談閉包。
以前也寫過幾篇文章,可以參考下 閉包初窺 以及 閉包拾遺 & 垃圾回收機制。
十八
What will be the output of the following code:
1 2 3 |
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, i * 1000 ); } |
Explain your answer. How could the use of closures help here?
以上程式碼輸出什麼?如何能輸出期望值?
很顯然,輸出都是 5。這題和第十三題有些類似,用立即執行函式+閉包即可。
1 2 3 4 5 |
for (var i = 0; i < 5; i++) { !function(i) { setTimeout(function() { console.log(i); }, i * 1000 ); }(i) } |
還有種優雅的解法,使用 bind:
1 2 3 |
for(var i = 0; i < 5; i++) { setTimeout(console.log.bind(console, i), i * 1000); } |
十九
What would the following lines of code output to the console?
1 2 3 4 |
console.log("0 || 1 = "+(0 || 1)); console.log("1 || 2 = "+(1 || 2)); console.log("0 & 1 = "+(0 && 1)); console.log("1 & 2 = "+(1 && 2)); |
以上程式碼輸出什麼?
||
和 &&
是短路運算子。先說說 ||,如果前面變數值為 false(包括 0、null、undefined、flase、空字串等等),則返回後面變數值,否則返回前面變數值。&& 恰恰相反,如果前面變數為 false,則返回前面變數值,否則返回後面變數值。
注意不要和位運算操作符 |
以及 &
搞混淆了。
二十
What will be the output when the following code is executed? Explain.
1 2 |
console.log(false == '0') console.log(false === '0') |
==
和 ===
的區別, 後者是全等,只有兩個值完全相同(或者兩個物件引用相同)時,才會返回 true,而前者在比較時會進行隱式的轉換。
二十一
What is the output out of the following code? Explain your answer.
1 2 3 4 5 6 7 8 |
var a={}, b={key:'b'}, c={key:'c'}; a[b]=123; a[c]=456; console.log(a[b]); |
一道有趣的題目,答案是 456。
我們知道,Javascript 中物件的 key 值,一定會是一個 string 值,如果不是,則會隱式地進行轉換。當執行到 a[b]=123]
時,b 並不是一個 string 值,將 b 執行 toString() 方法轉換(得到 “[object Object]”),a[c] 也是相同道理。所以程式碼其實可以看做這樣執行:
1 2 3 4 5 6 7 8 9 10 11 |
var a={}, b={key:'b'}, c={key:'c'}; // a[b]=123; a["[object Object]"]=123; // a[c]=456; a["[object Object]"]=456; console.log(a["[object Object]"]); |
這樣就一目瞭然了。
二十二
What will the following code output to the console:
1 |
console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10)); |
輸出什麼?
其實可以寫成這樣,清楚些:
1 2 3 4 5 |
var ans = (function f(n){ return ((n > 1) ? n * f(n-1) : n) })(10); console.log(ans); |
其實就是一個立即執行函式+遞迴,求個階乘而已(10!)。給立即執行函式加了個名字 f,方便在遞迴裡呼叫,其實完全可以用 arguments.callee
代替:
1 2 3 4 5 |
var ans = (function(n){ return ((n > 1) ? n * arguments.callee(n-1) : n) })(10); console.log(ans); |
二十三
Consider the code snippet below. What will the console output be and why?
1 2 3 4 5 |
(function(x) { return (function(y) { console.log(x); })(2) })(1); |
輸出什麼?
顯然是 1,閉包,能引用函式外的變數。
改成這樣呢?
1 2 3 4 5 |
(function(y) { return (function(y) { console.log(y); })(2) })(1); |
二十四
What will the following code output to the console and why:
1 2 3 4 5 6 7 8 9 10 11 |
var hero = { _name: 'John Doe', getSecretIdentity: function (){ return this._name; } }; var stoleSecretIdentity = hero.getSecretIdentity; console.log(stoleSecretIdentity()); console.log(hero.getSecretIdentity()); |
What is the issue with this code and how can it be fixed.
執行第一次輸出時,this 指向了 window,如何規避這個問題?用 bind 繫結 this 指向,具體可以參考 ECMAScript 5(ES5)中bind方法簡介備忘,注意低版本 IE 的相容。
1 |
var stoleSecretIdentity = hero.getSecretIdentity.bind(hero); |
二十五
Create a function that, given a DOM Element on the page, will visit the element itself and all of its descendents (not just its immediate children). For each element visited, the function should pass that element to a provided callback function.
The arguments to the function should be:
- a DOM element
- a callback function (that takes a DOM element as its argument)
遍歷 DOM 樹,不難,深度優先搜尋即可。
1 2 3 4 5 6 7 |
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 } } |
這道題可以擴充,先序遍歷 DOM樹,中序遍歷,甚至後序遍歷的結果是?具體可以參考前文 二叉樹三種遍歷的遞迴和迭代解法,都是樹,原理是一樣一樣的。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!