44 道 JS 難題,做對(duì)一半就是高手

國(guó)外某網(wǎng)站給出了44道JS難題,試著做了下,只做對(duì)了17道。這些題涉及面非常廣,涵蓋JS原型、函數(shù)細(xì)節(jié)、強(qiáng)制轉(zhuǎn)換、閉包等知識(shí),而且都是非常細(xì)節(jié)的東西,透過(guò)這些小細(xì)節(jié)可以折射出很多高級(jí)的JS知識(shí)點(diǎn)。

你可以通過(guò)傳送門(http://javascript-puzzlers.herokuapp.com/)先去測(cè)試一下你的水平,然后回來(lái)看看我的解析。為了詳細(xì)解釋這些細(xì)節(jié),我也查閱了不少資料,彌補(bǔ)了很多JS知識(shí)盲點(diǎn)。

1. parseInt 遇上 map
["1", "2", "3"].map(parseInt)

// A. ["1", "2", "3"]
// B. [1, 2, 3]
// C. [0, 1, 2]
// D. other
答案是D。實(shí)際上返回的結(jié)果是 [1, NaN, NaN] ,因?yàn)?parseInt 函數(shù)只需要兩個(gè)參數(shù) parseInt(value, radix) ,而 map 的回調(diào)函數(shù)需要三個(gè)參數(shù) callback(currentValue, index, array)。

MDN文檔中指明 parseInt 第二個(gè)參數(shù)是一個(gè)2到36之間的整數(shù)值,用于指定轉(zhuǎn)換中采用的基數(shù)。如果省略該參數(shù)或其值為0,則數(shù)字將以10為基礎(chǔ)來(lái)解析。如果該參數(shù)小于2或者大于36,則 parseInt 返回 NaN。此外,轉(zhuǎn)換失敗也會(huì)返回 NaN。

現(xiàn)在來(lái)分析問(wèn)題。parseInt("1", 0) 的結(jié)果是當(dāng)作十進(jìn)制來(lái)解析,返回 1;parseInt("2", 1) 的第二個(gè)參數(shù)非法,返回 NaN;parseInt("3", 2) 在二進(jìn)制中,"3" 是非法字符,轉(zhuǎn)換失敗,返回 NaN。

參考資料:

MDN: Array.prototype.map()

MDN: parseInt

2. 神奇的null
[typeof null, null instanceof Object]

// A. ["object", false]
// B. [null, false]
// C. ["object", true]
// D. other
答案是A。在MDN關(guān)于 null 的文檔中也特別指出來(lái)了,typeof null 的結(jié)果是 "object",它是ECMAScript的bug,其實(shí)應(yīng)該是 "null"。但這個(gè)bug由來(lái)已久,在JavaScript中已經(jīng)存在了將近二十年,也許永遠(yuǎn)不會(huì)修復(fù),因?yàn)檫@牽扯到太多的Web系統(tǒng),修復(fù)它會(huì)產(chǎn)生更多的bug,令許多系統(tǒng)無(wú)法正常工作。

而 instanceof 運(yùn)算符是用來(lái)測(cè)試一個(gè)對(duì)象在其原型鏈構(gòu)造函數(shù)上是否具有 prototype 屬性,null 值并不是以 Object 原型創(chuàng)建出來(lái)的,所以 null instanceof Object 返回 false。

參考資料:

MDN:null

MDN:instanceof

CSDN博客:null instanceof Object 求解?

3. 憤怒的reduce
[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ]

// A. an error
// B. [9, 0]
// C. [9, NaN]
// D. [9, undefined]
答案是A。MDN文檔中關(guān)于 Array.prototype.reduce() 寫得很清楚:

如果數(shù)組為空并且沒有提供initialValue, 會(huì)拋出TypeError 。如果數(shù)組僅有一個(gè)元素(無(wú)論位置如何)并且沒有提供initialValue, 或者有提供initialValue但是數(shù)組為空,那么此唯一值將被返回并且callback不會(huì)被執(zhí)行。

參考資料:

MDN:Array.prototype.reduce()
4. 該死的優(yōu)先級(jí)
var val = 'smtg';
console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing');

// A. Value is Something
// B. Value is Nothing
// C. NaN
// D. other
答案是D。實(shí)際上輸出 "Something",因?yàn)?+ 的優(yōu)先級(jí)比條件運(yùn)算符 condition ? val1 : val2 的優(yōu)先級(jí)高。

參考資料:

MDN:運(yùn)算符優(yōu)先級(jí)
5. 神鬼莫測(cè)之變量提升
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??慈缦翸DN官方文檔的解釋:

在 JavaScript中, functions 和 variables 會(huì)被提升。變量提升是JavaScript將聲明移至作用域 scope (全局域或者當(dāng)前函數(shù)作用域) 頂部的行為。

這意味著你可以在聲明一個(gè)函數(shù)或變量之前引用它,或者可以說(shuō):一個(gè)變量或函數(shù)可以在它被引用之后聲明。

所以,上面的代碼與下面這段代碼是等價(jià)的:

var name = 'World!';
(function () {
    var name;
    if (typeof name === 'undefined') {
      name = 'Jack';
      console.log('Goodbye ' + name);
    } else {
      console.log('Hello ' + name);
    }
})();
參考資料:

MDN:變量提升

這篇博客解釋的比較詳細(xì):js變量提升

6. 死循環(huán)陷阱
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,所以這個(gè)循環(huán)無(wú)法終止。

7. 過(guò)濾器魔法
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??碝DN官方文檔的描述:

filter 為數(shù)組中的每個(gè)元素調(diào)用一次 callback 函數(shù),并利用所有使得 callback 返回 true 或 等價(jià)于 true 的值 的元素創(chuàng)建一個(gè)新數(shù)組。callback 只會(huì)在已經(jīng)賦值的索引上被調(diào)用,對(duì)于那些已經(jīng)被刪除或者從未被賦值的索引不會(huì)被調(diào)用。那些沒有通過(guò) callback 測(cè)試的元素會(huì)被跳過(guò),不會(huì)被包含在新數(shù)組中。

參考資料:

MDN:Array.prototype.filter()
8. 警惕IEEE 754標(biāo)準(zhǔn)
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中采用雙精度浮點(diǎn)數(shù)格式,即IEEE 754標(biāo)準(zhǔn)。在該格式下,有些數(shù)字無(wú)法表示出來(lái),比如:0.1 + 0.2 = 0.30000000000000004 ,這不是JavaScript的鍋,所有采用該標(biāo)準(zhǔn)的語(yǔ)言都有這個(gè)問(wèn)題,比如:Java、Python等。

參考資料:

Wiki:Double-precision floating-point format
9. 字符串陷阱
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 內(nèi)部使用嚴(yán)格相等 === 進(jìn)行判斷,并且 new String("A") 返回的是一個(gè)對(duì)象,而 String("A") 則是直接返回字符串 "A"。你也可以參考MDN中對(duì)原始字符串和String對(duì)象的區(qū)分:

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.






參考資料:

MDN:String
10. 再一次的字符串陷阱

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 關(guān)鍵字,所以直接返回字符串,實(shí)際上,typeof string("A") === "string" 的結(jié)果是 true。解釋參見第9條的解釋。

11. 并非都是奇偶
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,求余運(yùn)算符會(huì)保留符號(hào),所以只有 isEven 的判斷是可靠的。

12. parseInt小賊
parseInt(3, 8);
parseInt(3, 2);
parseInt(3, 0);

// A. 3, 3, 3
// B. 3, 3, NaN
// C. 3, NaN, NaN
// D. other
答案是D。實(shí)際結(jié)果是 3, NaN, 3,這個(gè)在第一個(gè)問(wèn)題中解釋的很清楚了。

13. 數(shù)組原型是數(shù)組
Array.isArray( Array.prototype )

// A. true
// B. false
// C. error
// D. other
答案是A。一個(gè)鮮為人知的事實(shí):其實(shí) Array.prototype 也是一個(gè)數(shù)組。這點(diǎn)在MDN文檔中提到過(guò)。

參考資料:

MDN:Array.isArray()
14. 一言難盡的強(qiáng)制轉(zhuǎn)換
var a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("wut");
}

// A. true
// B. false
// C. "wut"
// D. other
答案是B。這個(gè)是JavaScript中強(qiáng)制轉(zhuǎn)換的經(jīng)典案例,關(guān)于強(qiáng)制轉(zhuǎn)換不是一兩句話可以跟你說(shuō)清楚的,我建議你系統(tǒng)性的學(xué)習(xí)一下,推薦你看看《你不知道的JavaScript-中卷》這本書,如果不舍得買書,github上有英文原版:You-Dont-Know-JS,深入理解之后你就是高手了。

好了,回到當(dāng)前這個(gè)問(wèn)題。當(dāng) [0] 需要被強(qiáng)制轉(zhuǎn)成 Boolean 的時(shí)候會(huì)被認(rèn)為是 true。所以進(jìn)入第一個(gè) if 語(yǔ)句,而 a == true 的轉(zhuǎn)換規(guī)則在ES5規(guī)范的第11.9.3節(jié)中已經(jīng)定義過(guò),你可以自己詳細(xì)探索下。

規(guī)范指出,== 相等中,如果有一個(gè)操作數(shù)是布爾類型,會(huì)先把他轉(zhuǎn)成數(shù)字,所以比較變成了 [0] == 1;同時(shí)規(guī)范指出如果其他類型和數(shù)字比較,會(huì)嘗試把這個(gè)類型轉(zhuǎn)成數(shù)字再進(jìn)行寬松比較,而對(duì)象(數(shù)組也是對(duì)象)會(huì)先調(diào)用它的 toString() 方法,此時(shí) [0] 會(huì)變成 "0",然后將字符串 "0" 轉(zhuǎn)成數(shù)字 0,而 0 == 1 的結(jié)果顯然是 false。

參考資料:

ES5規(guī)范:11.9.3

《你不知道的JavaScript-中卷》

15. 撒旦之子“==”
[]==[]

// A. true
// B. false
// C. error
// D. other
答案是B。ES5規(guī)范11.9.3.1-f指出:如果比較的兩個(gè)對(duì)象指向的是同一個(gè)對(duì)象,就返回 true,否則就返回 false,顯然,這是兩個(gè)不同的數(shù)組對(duì)象。

參考資料:

ES5規(guī)范:11.9.3.1

《你不知道的JavaScript-中卷》

16. 加號(hào) VS 減號(hào)
'5' + 3;
'5' - 3;

// A. "53", 2
// B. 8, 2
// C. error
// D. other
答案是A。"5" + 2 = "52" 很好理解,+ 運(yùn)算符中只要有一個(gè)是字符串,就會(huì)變成字符串拼接操作。你不知道的是,- 運(yùn)算符要求兩個(gè)操作數(shù)都是數(shù)字,如果不是,會(huì)強(qiáng)制轉(zhuǎn)換成數(shù)字,所以結(jié)果就變成了 5 - 2 = 3。

參考資料:

《你不知道的JavaScript-中卷》,第四章:4.4.2 字符串和數(shù)字之間的隱式強(qiáng)制類型轉(zhuǎn)換
17. 打死那個(gè)瘋子
1 + - + + + - + 1

// A. 2
// B. 1
// C. error
// D. other
答案是A。這個(gè)只能出現(xiàn)在示例代碼中,如果你發(fā)現(xiàn)哪個(gè)瘋子寫了這個(gè)在生產(chǎn)代碼中,打死他就行了。你只要知道 + 1 = 1和- 1 = -1,注意符號(hào)之間的空格。兩個(gè)減號(hào)抵消,所以最終結(jié)果等效于 1 + 1 = 2。或者你也可以在符號(hào)之間插入 0 來(lái)理解,即 1 + 0 - 0 + 0 + 0 + 0 - 0 + 1,這樣你就一目了然了吧!千萬(wàn)別寫這樣的代碼,因?yàn)榭赡軙?huì)被打死!

18. 淘氣的map
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。實(shí)際上結(jié)果是 ["1", undefined x 2],因?yàn)橐?guī)范寫得很清楚:

map 方法會(huì)給原數(shù)組中的每個(gè)元素都按順序調(diào)用一次 callback 函數(shù)。callback 每次執(zhí)行后的返回值組合起來(lái)形成一個(gè)新數(shù)組。callback 函數(shù)只會(huì)在有值的索引上被調(diào)用;那些從來(lái)沒被賦過(guò)值或者使用 delete 刪除的索引則不會(huì)被調(diào)用。






參考資料:

MDN: Array.prototype.map()
19. 統(tǒng)統(tǒng)算我的
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。實(shí)際上結(jié)果是 21。在JavaScript中,參數(shù)變量和 arguments 是雙向綁定的。改變參數(shù)變量,arguments 中的值會(huì)立即改變;而改變 arguments 中的值,參數(shù)變量也會(huì)對(duì)應(yīng)改變。

20. 損失精度的IEEE 754
var a = 111111111111111110000;
var b = 1111;
console.log(a + b);

// A. 111111111111111111111
// B. 111111111111111110000
// C. NaN
// D. Infinity
答案是B。這是IEEE 754規(guī)范的黑鍋,不是JavaScript的問(wèn)題。表示這么大的數(shù)占用過(guò)多位數(shù),會(huì)丟失精度,學(xué)過(guò)計(jì)算機(jī)組成原理的應(yīng)該知道是怎么回事。

參考資料:

Wiki:Double-precision floating-point format
21. 反轉(zhuǎn)世界
var x = [].reverse;
x();

// A. []
// B. undefined
// C. error
// D. window
答案是D。MDN規(guī)范關(guān)于 reverse 的描述:

reverse 方法顛倒數(shù)組中元素的位置,并返回該數(shù)組的引用。

而這里調(diào)用的時(shí)候沒有制定數(shù)組,所以默認(rèn)的 this 就是 window,所以最后結(jié)果返回的是 window。

參考資料:

MDN:Array.prototype.reverse()
22. 最小的正值
Number.MIN_VALUE > 0

// A. false
// B. true
// C. error
// D. other
答案是B??匆?guī)范描述吧:

MIN_VALUE屬性是 JavaScript 里最接近 0 的正值,而不是最小的負(fù)值。

MIN_VALUE的值約為 5e-324。小于 MIN_VALUE
("underflow values") 的值將會(huì)轉(zhuǎn)換為 0。

因?yàn)?MIN_VALUE是 Number 的一個(gè)靜態(tài)屬性,因此應(yīng)該直接使用:Number.MIN_VALUE,而不是作為一個(gè)創(chuàng)建的 Number實(shí)例的屬性。

參考資料:

MDN:Number.MIN_VALUE
23. 謹(jǐn)記優(yōu)先級(jí)
[1 < 2 < 3, 3 < 2 < 1]

// A. [true, true]
// B. [true, false]
// C. error
// D. other
答案是A。<和>的優(yōu)先級(jí)都是從左到右,所以 1 < 2 < 3 會(huì)先比較 1 < 2,這會(huì)得到 true,但是 < 要求比較的兩邊都是數(shù)字,所以會(huì)發(fā)生隱式強(qiáng)制轉(zhuǎn)換,將 true 轉(zhuǎn)換成 1,所以最后就變成了比較 1 < 3,結(jié)果顯然為 true。同理可以分析后者。

參考資料:

MDN:運(yùn)算符優(yōu)先級(jí)
24. 坑爹中的戰(zhàn)斗機(jī)
// the most classic wtf
2 == [[[2]]]

// A. true
// B. false
// C. undefined
// D. other
答案是A。根據(jù)ES5規(guī)范,如果比較的兩個(gè)值中有一個(gè)是數(shù)字類型,就會(huì)嘗試將另外一個(gè)值強(qiáng)制轉(zhuǎn)換成數(shù)字,再進(jìn)行比較。而數(shù)組強(qiáng)制轉(zhuǎn)換成數(shù)字的過(guò)程會(huì)先調(diào)用它的 toString方法轉(zhuǎn)成字符串,然后再轉(zhuǎn)成數(shù)字。所以 [2]會(huì)被轉(zhuǎn)成 "2",然后遞歸調(diào)用,最終 [[[2]]] 會(huì)被轉(zhuǎn)成數(shù)字 2。

25. 小數(shù)點(diǎn)魔術(shù)
3.toString();
3..toString();
3...toString();

// A. "3", error, error
// B. "3", "3.0", error
// C. error, "3", error
// D. other
答案是C。點(diǎn)運(yùn)算符會(huì)被優(yōu)先識(shí)別為數(shù)字常量的一部分,然后才是對(duì)象屬性訪問(wèn)符。所以 3.toString() 實(shí)際上被JS引擎解析成 (3.)toString(),顯然會(huì)出現(xiàn)語(yǔ)法錯(cuò)誤。但是如果你這么寫 (3).toString(),人為加上括號(hào),這就是合法的。

26. 自動(dòng)提升為全局變量
(function() {
  var x = y = 1;
})();
console.log(y);
console.log(x);

// A. 1, 1
// B. error, error
// C. 1, error
// D. other
答案是C。很經(jīng)典的例子,在函數(shù)中沒有用 var 聲明變量 y,所以 y 會(huì)被自動(dòng)創(chuàng)建在全局變量 window下面,所以在函數(shù)外面也可以訪問(wèn)得到。而 x 由于被 var 聲明過(guò),所以在函數(shù)外部是無(wú)法訪問(wèn)的。

27. 正則表達(dá)式實(shí)例
var a = /123/;
var b = /123/;
a == b;
a === b;

// A. true, true
// B. true, false
// C. false, false
// D. other
答案是C。每個(gè)字面的正則表達(dá)式都是一個(gè)單獨(dú)的實(shí)例,即使它們的內(nèi)容相同。

28. 數(shù)組也愛比大小
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。數(shù)組也是對(duì)象,ES5規(guī)范指出如果兩個(gè)對(duì)象進(jìn)行相等比較,只有在它們指向同一個(gè)對(duì)象的情況下才會(huì)返回 true,其他情況都返回 false。而對(duì)象進(jìn)行大小比較,會(huì)調(diào)用 toString 方法轉(zhuǎn)成字符串進(jìn)行比較,所以結(jié)果就變成了字符串 "1,2,3" 和 "1,2,4" 按照字典序進(jìn)行比較了(你若不信,可以重現(xiàn)兩個(gè)變量的 toString 方法,進(jìn)行測(cè)試)。

29. 原型把戲
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。對(duì)象是沒有 prototype 屬性的,所以 a.prototype 是 undefined,但我們可以通過(guò) Object.getPrototypeOf 方法來(lái)獲取一個(gè)對(duì)象的原型。

30. 構(gòu)造函數(shù)的函數(shù)
function f() {}
var a = f.prototype;
var b = Object.getPrototypeOf(f);
a === b;

// A. true
// B. false
// C. null
// D. other
答案是B。這個(gè)解釋起來(lái)有點(diǎn)繞口,我們先來(lái)看另外一段代碼:

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
首先你要明白,任何函數(shù)都是 Function 的實(shí)例,而p是函數(shù) Person 的實(shí)例,Object.getPrototypeOf 會(huì)獲取構(gòu)造當(dāng)前對(duì)象的原型。

所以 Object.getPrototypeOf(p) === Person.prototype,而 Object.getPrototypeOf(Person) === Function.prototype,所以答案就很明顯了。我解釋的不是很好,如果讀者有更好的解釋,歡迎評(píng)論。






31. 禁止修改函數(shù)名
function foo() {}
var oldName = foo.name;
foo.name = "bar";
[oldName, foo.name];

// A. error
// B. ["", ""]
// C. ["foo", "foo"]
// D. ["foo", "bar"]
答案是C。函數(shù)名是禁止修改的,規(guī)范寫的很清楚,所以這里的修改無(wú)效。

參考資料:

MDN:Function.name
32. 替換陷阱
"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 方法第二個(gè)參數(shù)是一個(gè)函數(shù),則會(huì)在匹配的時(shí)候多次調(diào)用,第一個(gè)參數(shù)是匹配的字符串,第二個(gè)參數(shù)是匹配字符串的下標(biāo)。所以變成了調(diào)用 parseInt(1, 0)、parseInt(2, 2)和parseInt(3, 4),結(jié)果你就懂了。

參考資料:

MDN:String.prototype.replace()
33. Function的名字
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。根據(jù)第30題的解釋,我們知道代碼中的 parent 實(shí)際上就是 Function.prototype,而它在控制臺(tái)中輸出為:

function () {
  [native code]
}
它的 name 屬性是 "",所以你 eval("")是得不到任何東西的。

34. 正則測(cè)試陷阱
var lowerCaseOnly = /^[a-z]+$/;
[lowerCaseOnly.test(null), lowerCaseOnly.test()]

// A. [true, false]
// B. error
// C. [true, true]
// D. [false, true]
答案是C。test 方法的參數(shù)如果不是字符串,會(huì)經(jīng)過(guò)抽象 ToString操作強(qiáng)制轉(zhuǎn)成字符串,因此實(shí)際上測(cè)試的是字符串 "null" 和 "undefined"。

35. 逗號(hào)定義數(shù)組
[,,,].join(", ")

// A. ", , , "
// B. "undefined, undefined, undefined, undefined"
// C. ", , "
// D. ""
答案是C。JavaScript允許用逗號(hào)來(lái)定義數(shù)組,得到的數(shù)組是含有3個(gè) undefined 值的數(shù)組。MDN關(guān)于 join 方法的描述:

所有的數(shù)組元素被轉(zhuǎn)換成字符串,再用一個(gè)分隔符將這些字符串連接起來(lái)。如果元素是undefined 或者null, 則會(huì)轉(zhuǎn)化成空字符串。

參考資料:

MDN:Array.prototype.join()
36. 保留字 class
var a = {class: "Animal", name: "Fido"};
console.log(a.class);

// A. "Animal"
// B. Object
// C. an error
// D. other
答案是D。實(shí)際上真正的答案取決于瀏覽器。class 是保留字,但是在Chrome、Firefox和Opera中可以作為屬性名稱,在IE中是禁止的。另一方面,其實(shí)所有瀏覽器基本接受大部分的關(guān)鍵字(如:int、private、throws等)作為變量名,而class是禁止的。

37. 無(wú)效日期
var a = new Date("epoch");

// A. Thu Jan 01 1970 01:00:00 GMT+0100(CET)
// B. current time
// C. error
// D. other
答案是D。實(shí)際結(jié)果是 Invalid Date,它實(shí)際上是一個(gè)Date對(duì)象,因?yàn)?a instance Date 的結(jié)果是 true,但是它是無(wú)效的Date。Date對(duì)象內(nèi)部是用一個(gè)數(shù)字來(lái)存儲(chǔ)時(shí)間的,在這個(gè)例子中,這個(gè)數(shù)字是 NaN。

38. 神鬼莫測(cè)的函數(shù)長(zhǎng)度
var a = Function.length;
var b = new Function().length;
console.log(a === b);

// A. true
// B. false
// C. error
// D. other
答案是B。實(shí)際上a的值是1,b的值是0。還是繼續(xù)來(lái)看MDN文檔關(guān)于 Function.length 的描述吧!Function構(gòu)造器的屬性:

Function 構(gòu)造器本身也是個(gè)Function。他的 length 屬性值為 1 。該屬性 Writable: false, Enumerable: false, Configurable: true。

Function原型對(duì)象的屬性:

Function原型對(duì)象的 length 屬性值為 0 。

所以,在本例中,a代表的是 Function 構(gòu)造器的 length 屬性,而b代表的是 Function 原型的 length 屬性。參考資料:

MDN:Function.length
39. Date的面具
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關(guān)于Date對(duì)象的注意點(diǎn):

需要注意的是只能通過(guò)調(diào)用 Date 構(gòu)造函數(shù)來(lái)實(shí)例化日期對(duì)象:以常規(guī)函數(shù)調(diào)用它(即不加 new 操作符)將會(huì)返回一個(gè)字符串,而不是一個(gè)日期對(duì)象。另外,不像其他JavaScript 類型,Date 對(duì)象沒有字面量格式。

所以a是字符串,b和c是Date對(duì)象,并且b代表的是1970年那個(gè)初始化時(shí)間,而c代表的是當(dāng)前時(shí)間。

參考資料:

MDN:Date
40. min與max共舞
var min = Math.min();
var max = Math.max();
console.log(min < max);

// A. true
// B. false
// C. error
// D. other
答案是B??碝DN文檔,對(duì) Math.min的描述:

如果沒有參數(shù),結(jié)果為Infinity。

對(duì) Math.max 的描述:

如果沒有參數(shù),結(jié)果為-Infinity。

參考資料:

MDN:Math.min

MDN:Math.max

41. 警惕全局匹配
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??碝DN關(guān)于 exec 方法的描述:

當(dāng)正則表達(dá)式使用 "g" 標(biāo)志時(shí),可以多次執(zhí)行 exec 方法來(lái)查找同一個(gè)字符串中的成功匹配。當(dāng)你這樣做時(shí),查找將從正則表達(dá)式的  lastIndex 屬性指定的位置開始。

所以a3的值為 null。

參考資料:

MDN:RegExp.prototype.exec()
42. 最熟悉的陌生人
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關(guān)于Date的一個(gè)注意事項(xiàng):

當(dāng)Date作為構(gòu)造函數(shù)調(diào)用并傳入多個(gè)參數(shù)時(shí),如果數(shù)值大于合理范圍時(shí)(如月份為13或者分鐘數(shù)為70),相鄰的數(shù)值會(huì)被調(diào)整。比如 new Date(2013, 13, 1)等于new Date(2014, 1, 1),它們都表示日期2014-02-01(注意月份是從0開始的)。其他數(shù)值也是類似,new Date(2013, 2, 1, 0, 70)等于new Date(2013, 2, 1, 1, 10),都表示時(shí)間2013-03-01T01:10:00。

此外,getDay 返回指定日期對(duì)象的星期中的第幾天(0~6),所以,你懂的。參考資料:

MDN:Date
43. 匹配隱式轉(zhuǎn)換
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對(duì) match 方法的描述:

如果傳入一個(gè)非正則表達(dá)式對(duì)象,則會(huì)隱式地使用 new RegExp(obj)
將其轉(zhuǎn)換為正則表達(dá)式對(duì)象。

所以我們的字符串 ".gif" 會(huì)被轉(zhuǎn)換成正則對(duì)象 /.gif/,會(huì)匹配到 "/gif"。

參考資料:

MDN:String.prototype.match()
44. 重復(fù)聲明變量
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。一個(gè)變量在同一作用域中已經(jīng)聲明過(guò),會(huì)自動(dòng)移除 var 聲明,但是賦值操作依舊保留,結(jié)合前面提到的變量提升機(jī)制,你就明白了。

參考資料:

MDN:var
作者:康斌

https://www.jianshu.com/p/e161bd720e64






作者:康斌

歡迎關(guān)注微信公眾號(hào) :前端開發(fā)愛好者

添加好友備注【進(jìn)階學(xué)習(xí)】拉你進(jìn)技術(shù)交流群