國外某網站給出了44道JS難題,試着作了下,只作對了17道。這些題涉及面很是廣,涵蓋JS原型、函數細節、強制轉換、閉包等知識,並且都是很是細節的東西,透過這些小細節能夠折射出不少高級的JS知識點。javascript
你能夠經過傳送門先去測試一下你的水平,而後回來看看個人解析。爲了詳細解釋這些細節,我也查閱了很多資料,彌補了不少JS知識盲點。php
["1", "2", "3"].map(parseInt) // A. ["1", "2", "3"] // B. [1, 2, 3] // C. [0, 1, 2] // D. other
答案是D。實際上返回的結果是 [1, NaN, NaN]
,由於 parseInt
函數只須要兩個參數 parseInt(value, radix)
,而 map
的回調函數須要三個參數 callback(currentValue, index, array)
。MDN文檔中指明 parseInt
第二個參數是一個2到36之間的整數值,用於指定轉換中採用的基數。若是省略該參數或其值爲0,則數字將以10爲基礎來解析。若是該參數小於2或者大於36,則 parseInt
返回 NaN
。此外,轉換失敗也會返回 NaN
。java
如今來分析問題。parseInt("1", 0)
的結果是看成十進制來解析,返回 1
;parseInt("2", 1)
的第二個參數非法,返回 NaN
;parseInt("3", 2)
在二進制中,"3"
是非法字符,轉換失敗,返回 NaN
。git
參考資料:github
[typeof null, null instanceof Object] // A. ["object", false] // B. [null, false] // C. ["object", true] // D. other
答案是A。在MDN關於 null
的文檔中也特別指出來了,typeof null
的結果是 "object"
,它是ECMAScript的bug,其實應該是 "null"
。但這個bug由來已久,在JavaScript中已經存在了將近二十年,也許永遠不會修復,由於這牽扯到太多的Web系統,修復它會產生更多的bug,令許多系統沒法正常工做。而 instanceof
運算符是用來測試一個對象在其原型鏈構造函數上是否具備 prototype
屬性,null
值並非以 Object
原型建立出來的,因此 null instanceof Object
返回 false
。正則表達式
參考資料:數組
[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ] // A. an error // B. [9, 0] // C. [9, NaN] // D. [9, undefined]
答案是A。MDN文檔中關於 Array.prototype.reduce()
寫得很清楚:瀏覽器
若是數組爲空而且沒有提供initialValue, 會拋出TypeError 。若是數組僅有一個元素(不管位置如何)而且沒有提供initialValue, 或者有提供initialValue可是數組爲空,那麼此惟一值將被返回而且callback不會被執行。bash
參考資料:閉包
var val = 'smtg'; console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing'); // A. Value is Something // B. Value is Nothing // C. NaN // D. other
答案是D。實際上輸出 "Something"
,由於 +
的優先級比條件運算符 condition ? val1 : val2
的優先級高。
參考資料:
var name = 'World!'; (function () { if (typeof name === 'undefined') { var name = 'Jack'; console.log('Goodbye ' + name); } else { console.log('Hello ' + name); } })(); // A. Goodbye Jack // B. Hello Jack // C. Hello undefined // D. Hello World
答案是A。看以下MDN官方文檔的解釋:
在 JavaScript中, functions 和 variables 會被提高。變量提高是JavaScript將聲明移至做用域 scope (全局域或者當前函數做用域) 頂部的行爲。
這意味着你能夠在聲明一個函數或變量以前引用它,或者能夠說:一個變量或函數能夠在它被引用以後聲明。
因此,上面的代碼與下面這段代碼是等價的:
var name = 'World!'; (function () { var name; if (typeof name === 'undefined') { name = 'Jack'; console.log('Goodbye ' + name); } else { console.log('Hello ' + name); } })();
參考資料:
var END = Math.pow(2, 53); var START = END - 100; var count = 0; for (var i = START; i <= END; i++) { count++; } console.log(count); // A. 0 // B. 100 // C. 101 // D. other
答案是D。在JavaScript中,2^53
是最大的值,沒有比這更大的值了。因此 2^53 + 1 == 2^53
,因此這個循環沒法終止。
var ary = [0,1,2]; ary[10] = 10; ary.filter(function(x) { return x === undefined; }); // A. [undefined x 7] // B. [0, 1, 2, 10] // C. [] // D. [undefined]
答案是C。看MDN官方文檔的描述:
filter 爲數組中的每一個元素調用一次 callback 函數,並利用全部使得 callback 返回 true 或 等價於 true 的值 的元素建立一個新數組。callback 只會在已經賦值的索引上被調用,對於那些已經被刪除或者從未被賦值的索引不會被調用。那些沒有經過 callback 測試的元素會被跳過,不會被包含在新數組中。
參考資料:
var two = 0.2; var one = 0.1; var eight = 0.8; var six = 0.6; [two - one == one, eight - six == two] // A. [true, false] // B. [false, false] // C. [true, false] // D. other
答案是C。JavaScript中採用雙精度浮點數格式,即IEEE 754標準。在該格式下,有些數字沒法表示出來,好比:0.1 + 0.2 = 0.30000000000000004
,這不是JavaScript的鍋,全部採用該標準的語言都有這個問題,好比:Java、Python等。
參考資料:
function showCase(value) { switch(value) { case 'A': console.log('Case A'); break; case 'B': console.log('Case B'); break; case undefined: console.log('undefined'); break; default: console.log('Do not know!'); } } showCase(new String('A')); // A. Case A // B. Case B // C. Do not know! // D. undefined
答案是C。在 switch
內部使用嚴格相等 ===
進行判斷,而且 new String("A")
返回的是一個對象,而 String("A")
則是直接返回字符串 "A"
。你也能夠參考MDN中對原始字符串和String對象的區分:
Note that JavaScript distinguishes between String objects and primitive string values. (The same is true of Boolean and Numbers.)
String literals (denoted by double or single quotes) and strings returned from String calls in a non-constructor context (i.e., without using the new keyword) are primitive strings. JavaScript automatically converts primitives to String objects, so that it's possible to use String object methods for primitive strings. In contexts where a method is to be invoked on a primitive string or a property lookup occurs, JavaScript will automatically wrap the string primitive and call the method or perform the property lookup.
參考資料:
function showCase(value) { switch(value) { case 'A': console.log('Case A'); break; case 'B': console.log('Case B'); break; case undefined: console.log('undefined'); break; default: console.log('Do not know!'); } } showCase(String('A')); // A. Case A // B. Case B // C. Do not know! // D. undefined
答案顯然是A。與上面惟一不一樣的是沒有使用 new
關鍵字,因此直接返回字符串,實際上,typeof string("A") === "string"
的結果是 true
。解釋參見第9條的解釋。
function isOdd(num) { return num % 2 == 1; } function isEven(num) { return num % 2 == 0; } function isSane(num) { return isEven(num) || isOdd(num); } var values = [7, 4, "13", -9, Infinity]; values.map(isSane); // A. [true, true, true, true, true] // B. [true, true, true, true, false] // C. [true, true, true, false, false] // D. [true, true, false, false, false]
答案是C。-9 % 2 = -1
以及 Infinity % 2 = NaN
,求餘運算符會保留符號,因此只有 isEven
的判斷是可靠的。
parseInt(3, 8); parseInt(3, 2); parseInt(3, 0); // A. 3, 3, 3 // B. 3, 3, NaN // C. 3, NaN, NaN // D. other
答案是D。實際結果是 3, NaN, 3
,這個在第一個問題中解釋的很清楚了。
Array.isArray( Array.prototype ) // A. true // B. false // C. error // D. other
答案是A。一個不爲人知的事實:其實 Array.prototype
也是一個數組。這點在MDN文檔中提到過。
參考資料:
var a = [0]; if ([0]) { console.log(a == true); } else { console.log("wut"); } // A. true // B. false // C. "wut" // D. other
答案是B。這個是JavaScript中強制轉換的經典案例,關於強制轉換不是一兩句話能夠跟你說清楚的,我建議你係統性的學習一下,推薦你看看《你不知道的JavaScript-中卷》這本書,若是不捨得買書,github上有英文原版:You-Dont-Know-JS,深刻理解以後你就是高手了。
好了,回到當前這個問題。當 [0]
須要被強制轉成 Boolean
的時候會被認爲是 true
。因此進入第一個 if
語句,而 a == true
的轉換規則在ES5規範的第11.9.3節中已經定義過,你能夠本身詳細探索下。
規範指出,==
相等中,若是有一個操做數是布爾類型,會先把他轉成數字,因此比較變成了 [0] == 1
;同時規範指出若是其餘類型和數字比較,會嘗試把這個類型轉成數字再進行寬鬆比較,而對象(數組也是對象)會先調用它的 toString()
方法,此時 [0]
會變成 "0"
,而後將字符串 "0"
轉成數字 0
,而 0 == 1
的結果顯然是 false
。
參考資料:
[]==[]
// A. true // B. false // C. error // D. other
答案是B。ES5規範11.9.3.1-f指出:若是比較的兩個對象指向的是同一個對象,就返回 true
,不然就返回 false
,顯然,這是兩個不一樣的數組對象。
參考資料:
'5' + 3; '5' - 3; // A. "53", 2 // B. 8, 2 // C. error // D. other
答案是A。"5" + 2 = "52"
很好理解,+
運算符中只要有一個是字符串,就會變成字符串拼接操做。你不知道的是,-
運算符要求兩個操做數都是數字,若是不是,會強制轉換成數字,因此結果就變成了 5 - 2 = 3
。
參考資料:
1 + - + + + - + 1 // A. 2 // B. 1 // C. error // D. other
答案是A。這個只能出如今示例代碼中,若是你發現哪一個瘋子寫了這個在生產代碼中,打死他就好了。你只要知道 + 1 = 1
和- 1 = -1
,注意符號之間的空格。兩個減號抵消,因此最終結果等效於 1 + 1 = 2
。或者你也能夠在符號之間插入 0
來理解,即 1 + 0 - 0 + 0 + 0 + 0 - 0 + 1
,這樣你就一目瞭然了吧!千萬別寫這樣的代碼,由於可能會被打死!
var ary = Array(3); ary[0] = 2; ary.map(function(elem) { return "1"; }); // A. [2, 1, 1] // B. ["1", "1", "1"] // C. [2, "1", "1"] // D. other
答案是D。實際上結果是 ["1", undefined x 2]
,由於規範寫得很清楚:
map 方法會給原數組中的每一個元素都按順序調用一次 callback 函數。callback 每次執行後的返回值組合起來造成一個新數組。 callback 函數只會在有值的索引上被調用;那些歷來沒被賦過值或者使用 delete 刪除的索引則不會被調用。
參考資料:
function sidEffecting(ary) { ary[0] = ary[2]; } function bar(a, b, c) { c = 10; sidEffecting(arguments); return a + b + c; } bar(1, 1, 1); // A. 3 // B. 12 // C. error // D. other
答案是D。實際上結果是 21
。在JavaScript中,參數變量和 arguments
是雙向綁定的。改變參數變量,arguments
中的值會當即改變;而改變 arguments
中的值,參數變量也會對應改變。
var a = 111111111111111110000; var b = 1111; console.log(a + b); // A. 111111111111111111111 // B. 111111111111111110000 // C. NaN // D. Infinity
答案是B。這是IEEE 754規範的黑鍋,不是JavaScript的問題。表示這麼大的數佔用過多位數,會丟失精度,學過計算機組成原理的應該知道是怎麼回事。
參考資料:
var x = [].reverse; x(); // A. [] // B. undefined // C. error // D. window
答案是D。MDN規範關於 reverse
的描述:
reverse 方法顛倒數組中元素的位置,並返回該數組的引用。
而這裏調用的時候沒有制定數組,因此默認的 this
就是 window
,因此最後結果返回的是 window
。
參考資料:
Number.MIN_VALUE > 0 // A. false // B. true // C. error // D. other
答案是B。看規範描述吧:
MIN_VALUE屬性是 JavaScript 裏最接近 0 的正值,而不是最小的負值。
MIN_VALUE的值約爲 5e-324。小於 MIN_VALUE
("underflow values") 的值將會轉換爲 0。
由於 MIN_VALUE是 Number 的一個靜態屬性,所以應該直接使用: Number.MIN_VALUE,而不是做爲一個建立的 Number實例的屬性。
參考資料:
[1 < 2 < 3, 3 < 2 < 1]
// A. [true, true] // B. [true, false] // C. error // D. other
答案是A。<
和>
的優先級都是從左到右,因此 1 < 2 < 3
會先比較 1 < 2
,這會獲得 true
,可是 <
要求比較的兩邊都是數字,因此會發生隱式強制轉換,將 true
轉換成 1
,因此最後就變成了比較 1 < 3
,結果顯然爲 true
。同理能夠分析後者。
參考資料:
// the most classic wtf 2 == [[[2]]] // A. true // B. false // C. undefined // D. other
答案是A。根據ES5規範,若是比較的兩個值中有一個是數字類型,就會嘗試將另一個值強制轉換成數字,再進行比較。而數組強制轉換成數字的過程會先調用它的 toString
方法轉成字符串,而後再轉成數字。因此 [2]
會被轉成 "2"
,而後遞歸調用,最終 [[[2]]]
會被轉成數字 2
。
3.toString();
3..toString();
3...toString();
// A. "3", error, error // B. "3", "3.0", error // C. error, "3", error // D. other
答案是C。點運算符會被優先識別爲數字常量的一部分,而後纔是對象屬性訪問符。因此 3.toString()
實際上被JS引擎解析成 (3.)toString()
,顯然會出現語法錯誤。可是若是你這麼寫 (3).toString()
,人爲加上括號,這就是合法的。
(function() { var x = y = 1; })(); console.log(y); console.log(x); // A. 1, 1 // B. error, error // C. 1, error // D. other
答案是C。很經典的例子,在函數中沒有用 var
聲明變量 y
,因此 y
會被自動建立在全局變量 window
下面,因此在函數外面也能夠訪問獲得。而 x
因爲被 var
聲明過,因此在函數外部是沒法訪問的。
var a = /123/; var b = /123/; a == b; a === b; // A. true, true // B. true, false // C. false, false // D. other
答案是C。每一個字面的正則表達式都是一個單獨的實例,即便它們的內容相同。
var a = [1, 2, 3];
var b = [1, 2, 3];
var c = [1, 2, 4];
a == b;
a === b;
a > c;
a < c;
// A. false, false, false, true // B. false, false, false, false // C. true, true, false, true // D. other
答案是A。數組也是對象,ES5規範指出若是兩個對象進行相等比較,只有在它們指向同一個對象的狀況下才會返回 true
,其餘狀況都返回 false
。而對象進行大小比較,會調用 toString
方法轉成字符串進行比較,因此結果就變成了字符串 "1,2,3"
和 "1,2,4"
按照字典序進行比較了(你若不信,能夠重現兩個變量的 toString
方法,進行測試)。
var a = {}; var b = Object.prototype; [a.prototype === b, Object.getPrototypeOf(a) == b] // A. [false, true] // B. [true, true] // C. [false, false] // D. other
答案是A。對象是沒有 prototype
屬性的,因此 a.prototype
是 undefined
,但咱們能夠經過 Object.getPrototypeOf
方法來獲取一個對象的原型。
function f() {} var a = f.prototype; var b = Object.getPrototypeOf(f); a === b; // A. true // B. false // C. null // D. other
答案是B。這個解釋起來有點繞口,咱們先來看另一段代碼:
function Person() {} var p = new Person(); var a = p.__proto__; var b = Object.getPrototypeOf(p); var c = Person.prototype; console.log(a === b, a === c, b === c); // true, true, true var d = Person.__proto__; var e = Object.getPrototypeOf(Person); var f = Function.prototype; console.log(d === e, d === f, e === f); // true, true, true
首先你要明白,任何函數都是 Function
的實例,而p是函數 Person
的實例,Object.getPrototypeOf
會獲取構造當前對象的原型。因此 Object.getPrototypeOf(p) === Person.prototype
,而 Object.getPrototypeOf(Person) === Function.prototype
,因此答案就很明顯了。
我解釋的不是很好,若是讀者有更好的解釋,歡迎評論。
function foo() {} var oldName = foo.name; foo.name = "bar"; [oldName, foo.name]; // A. error // B. ["", ""] // C. ["foo", "foo"] // D. ["foo", "bar"]
答案是C。函數名是禁止修改的,規範寫的很清楚,因此這裏的修改無效。
參考資料:
"1 2 3".replace(/\d/g, parseInt); // A. "1 2 3" // B. "0 1 2" // C. "NaN 2 3" // D. "1 NaN 3"
答案是D。若是 replace
方法第二個參數是一個函數,則會在匹配的時候屢次調用,第一個參數是匹配的字符串,第二個參數是匹配字符串的下標。因此變成了調用 parseInt(1, 0)
、parseInt(2, 2)
和parseInt(3, 4)
,結果你就懂了。
參考資料:
function f() {} var parent = Object.getPrototypeOf(f); console.log(f.name); console.log(parent.name); console.log(typeof eval(f.name)); console.log(typeof eval(parent.name)); // A. "f", "Empty", "function", "function" // B. "f", undefined, "function", error // C. "f", "Empty", "function", error // D. other
答案是C。根據第30題的解釋,咱們知道代碼中的 parent
實際上就是 Function.prototype
,而它在控制檯中輸出爲:
function () { [native code] }
它的 name
屬性是 ""
,因此你 eval("")
是得不到任何東西的。
var lowerCaseOnly = /^[a-z]+$/; [lowerCaseOnly.test(null), lowerCaseOnly.test()] // A. [true, false] // B. error // C. [true, true] // D. [false, true]
答案是C。test
方法的參數若是不是字符串,會通過抽象 ToString
操做強制轉成字符串,所以實際上測試的是字符串 "null"
和 "undefined"
。
[,,,].join(", ") // A. ", , , " // B. "undefined, undefined, undefined, undefined" // C. ", , " // D. ""
答案是C。JavaScript容許用逗號來定義數組,獲得的數組是含有3個 undefined
值的數組。MDN關於 join
方法的描述:
全部的數組元素被轉換成字符串,再用一個分隔符將這些字符串鏈接起來。若是元素是undefined 或者null, 則會轉化成空字符串。
參考資料:
var a = {class: "Animal", name: "Fido"}; console.log(a.class); // A. "Animal" // B. Object // C. an error // D. other
答案是D。實際上真正的答案取決於瀏覽器。class
是保留字,可是在Chrome、Firefox和Opera中能夠做爲屬性名稱,在IE中是禁止的。另外一方面,其實全部瀏覽器基本接受大部分的關鍵字(如:int
、private
、throws
等)做爲變量名,而class
是禁止的。
var a = new Date("epoch"); // A. Thu Jan 01 1970 01:00:00 GMT+0100(CET) // B. current time // C. error // D. other
答案是D。實際結果是 Invalid Date
,它其實是一個Date對象,由於 a instance Date
的結果是 true
,可是它是無效的Date。Date對象內部是用一個數字來存儲時間的,在這個例子中,這個數字是 NaN
。
var a = Function.length; var b = new Function().length; console.log(a === b); // A. true // B. false // C. error // D. other
答案是B。實際上a的值是1,b的值是0。仍是繼續來看MDN文檔關於 Function.length
的描述吧!
Function構造器的屬性:
Function 構造器自己也是個Function。他的 length 屬性值爲 1 。該屬性 Writable: false, Enumerable: false, Configurable: true。
Function原型對象的屬性:
Function原型對象的 length 屬性值爲 0 。
因此,在本例中,a表明的是 Function
構造器的 length
屬性,而b表明的是 Function
原型的 length
屬性。
參考資料:
var a = Date(0);
var b = new Date(0);
var c = new Date();
[a === b, b === c, a === c];
// A. [true, true, true] // B. [false, false, false] // C. [false, true, false] // D. [true, false, false]
答案是B。先看MDN關於Date對象的注意點:
須要注意的是隻能經過調用 Date 構造函數來實例化日期對象:以常規函數調用它(即不加 new 操做符)將會返回一個字符串,而不是一個日期對象。另外,不像其餘JavaScript 類型,Date 對象沒有字面量格式。
因此a是字符串,b和c是Date對象,而且b表明的是1970年那個初始化時間,而c表明的是當前時間。
參考資料:
var min = Math.min(); var max = Math.max(); console.log(min < max); // A. true // B. false // C. error // D. other
答案是B。看MDN文檔,對 Math.min
的描述:
若是沒有參數,結果爲Infinity。
對 Math.max
的描述:
若是沒有參數,結果爲-Infinity。
參考資料:
function captureOne(re, str) { var match = re.exec(str); return match && match[1]; } var numRe = /num=(\d+)/ig, wordRe = /word=(\w+)/i, a1 = captureOne(numRe, "num=1"), a2 = captureOne(wordRe, "word=1"), a3 = captureOne(numRe, "NUM=1"), a4 = captureOne(wordRe, "WORD=1"); [a1 === a2, a3 === a4] // A. [true, true] // B. [false, false] // C. [true, false] // D. [false, true]
答案是C。看MDN關於 exec
方法的描述:
當正則表達式使用 "g" 標誌時,能夠屢次執行 exec 方法來查找同一個字符串中的成功匹配。當你這樣作時,查找將從正則表達式的 lastIndex 屬性指定的位置開始。
因此a3的值爲 null
。
參考資料:
var a = new Date("2014-03-19"); var b = new Date(2014, 03, 19); [a.getDay() == b.getDay(), a.getMonth() == b.getMonth()] // A. [true, true] // B. [true, false] // C. [false, true] // D. [false, false]
答案是D。先看MDN關於Date的一個注意事項:
當Date做爲構造函數調用並傳入多個參數時,若是數值大於合理範圍時(如月份爲13或者分鐘數爲70),相鄰的數值會被調整。好比 new Date(2013, 13, 1)等於new Date(2014, 1, 1),它們都表示日期2014-02-01(注意月份是從0開始的)。其餘數值也是相似,new Date(2013, 2, 1, 0, 70)等於new Date(2013, 2, 1, 1, 10),都表示時間2013-03-01T01:10:00。
此外,getDay
返回指定日期對象的星期中的第幾天(0~6),因此,你懂的。
參考資料:
if("http://giftwrapped.com/picture.jpg".match(".gif")) { console.log("a gif file"); } else { console.log("not a gif file"); } // A. "a gif file" // B. "not a gif file" // C. error // D. other
答案是A。看MDN對 match
方法的描述:
若是傳入一個非正則表達式對象,則會隱式地使用 new RegExp(obj)
將其轉換爲正則表達式對象。
因此咱們的字符串 ".gif"
會被轉換成正則對象 /.gif/
,會匹配到 "/gif"
。
參考資料:
function foo(a) { var a; return a; } function bar(a) { var a = "bye"; return a; } [foo("hello"), bar("hello")] // A. ["hello", "hello"] // B. ["hello", "bye"] // C. ["bye", "bye"] // D. other
答案是B。一個變量在同一做用域中已經聲明過,會自動移除 var
聲明,可是賦值操做依舊保留,結合前面提到的變量提高機制,你就明白了。
參考資料: