44道JavaScript送命題

nd發表於2020-04-26

很久以前看過一個老外寫的帖子,JavaScript Puzzlers!,直譯就是JavaScript難題,裡面列舉了44道JavaScript選擇題,大部分都是讓人摸不著頭腦的題目,需要仔細琢磨一番才能得到正確答案。也有一些作者也沒有解釋清除,直接通過實驗給出答案了。

這44個問題是在ECMA 262(5.1)環境下,瀏覽器中試驗的,如果是node環境下可能不同。這是因為二者環境差異,比如node環境下頂層變數是global,瀏覽器環境下則是windows。

本文部分內容也參考了文章Javascript 變態題解析

1. map&parseInt傳參

["1", "2", "3"].map(parseInt)結果是什麼?

map方法指定一個回撥函式,重新建立一個由回撥函式返回值組成的新陣列。該方法的原型是:

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array 
}[, thisArg])

map接受2個引數,一個是回撥函式callback,一個是回撥函式的this值。

解釋如下:

  • callback:生成新陣列元素的函式,有三個引數
  • currentValue:callbac當前正在處理的元素
  • index:callback當前正在處理的當前元素的索引
  • array:map方法呼叫的陣列本身
  • thisArg:執行callback函式時值被當做this

Number.parseInt接受兩個引數,原型Number.parseInt(string[, radix]),一個是要解析的值,一般是字串,如果不是的話,使用toString方法將它轉化為字串。引數radix,是一個介於236之間的整數,如果省略該值或者為0,則按照10進位制來解析,也就是說預設值是10,如果是“0x”或者“0X”開頭,則以16進製為基數。如果小於2或者大於36,則parseInt返回NaN。

也就是說[].map(parseInt)這種寫法根本就是想當然的,本題相當於下面的三句:

parseInt('1', 0);
parseInt('2', 1);
parseInt('3', 2);

這三句只有第一句會把第二個引數0預設為10,剩下兩句都不滿足radix引數介於2到36之間,所有返回[1, NaN, NaN]。另外,如果想得到正確的結果,應該這樣寫["1", "2", "2"].map(i => parseInt(i))。

2. typeof和instanceof

執行[typeof null, null instanceof Object]這個表示式結果是什麼?,這個主要考察typeof,instanceof兩個操作符,前者是返回一個字串表示未經計算的運算元的型別,後者是判斷null的原型鏈中是否出現了Object的建構函式的property。

這兩個操作符用來判斷型別,前者常用來判斷字面量,後者用來判斷物件的型別,但是兩個都有缺陷,詳見另一篇文章《javascript中判斷資料型別》

但是null是一個比較特殊的值,type of null返回的是“objec”,這是因為JavaScript最初實現中,值是由一個表示型別的標籤和實際數值表示的。物件的型別標籤是0,由於null代表是空指標(大多數平臺下值為0x00),因此null的型別標籤是0,typeof null也就返回“object”。

null是所有原型鏈的最頂端,null instanceof Object返回false,假設null這個值有建構函式Null,obj instanceof Null才會返回true。

所以上面表示式返回["object", false]。

3.reduce&Math.pow傳參

表示式[ [3,2,1].reduce(Math.pow), [].reduce(Math.pow) ]返回什麼?

 和第1題有些類似。arr.reduce方法對陣列中每個元素執行一個自定義的reducer函式(升序執行),並將結果彙總為單個值,這個常常用來累加一個陣列中的數字。原型如下:

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]) 
  • callback:陣列中每個值要執行的函式,有四個引數。
  • accumulator:它是上一次回撥函式時得到的值,或者initialValue。
  • currentValue:陣列中正在處理的當前元素。
  • index:可選,陣列中正在處理的元素的索引,如果提供了initialValue,則起始索引號為0,否則從索引1開始。
  • array:呼叫reduce()的陣列
  • initialValue:作為第一次呼叫callback時的第一個引數的值。如果沒有提供初始值,則將使用陣列中的第一個元素。在沒有初始值的空陣列上呼叫reduce將報錯。

注意:如果沒有提供initialValue,reduce 會從索引1的地方開始執行 callback 方法,跳過第一個索引。如果提供initialValue,從索引0開始。

回撥函式第一次執行時,accumulator 和currentValue的取值有兩種情況:

  1. 如果呼叫reduce()時提供了initialValueaccumulator取值為initialValuecurrentValue取陣列中的第一個值;如果沒有提供 initialValue,那麼accumulator取陣列中的第一個值,currentValue取陣列中的第二個值。
  2. 如果陣列為空且沒有提供initialValue,會丟擲TypeError 。如果陣列僅有一個元素(無論位置如何)並且沒有提供initialValue, 或者有提供initialValue但是陣列為空,那麼此唯一值將被返回並且callback不會被執行。

Math.pow(base, exponent),返回基數base的指數exponent次冪,即baseexponent。

上面表示式呼叫reduce方法的時候沒有提供initialValue,從索引1開始執行,第一次執行的時候accumulator取arr[0],這裡是3,currentValue取第二個值,這裡是2,傳給Math.pow,得到9。

第二個表示式是在空陣列上呼叫reduce,並且沒有提供initialValue,所以丟擲錯誤:VM146:1 Uncaught TypeError: Reduce of empty array with no initial value at Array.reduce (<anonymous>)。

最後整個表示式的結果還是丟擲錯誤。

4. 優先順序

var val = 'smtg';
console.log('Value is ' + (val === 'smtg') ? 'Something' : 'Nothing'); 

上面的表示式輸出結果是什麼?這個問題考察的是加號和三元運算的優先順序,由於加號的優先順序高於三元表示式,所以實際執行的是:

console.log('Value is true' ? 'Something' : 'Nothing'); 

因此最後輸出“Something”。

5.變數提升問題

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

上面表示式輸出什麼?

這個是考察var變數提升問題,使用var申明的變數會提神到函式頂部,但是並不會初始化,這個是JavaScript內部機制。於是上面語句相當於:

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

name的宣告放在了函式頂部,但是值是undefined。因為程式碼又放在一個閉包裡,用外層那個name = “world”是不能訪問的,閉包有隔離變數的作用。最後,上面的語句輸出“Goodbye Jack”。

6. JavaScript能表示的最大數

var END = Math.pow(2, 53);
var START = END - 100;
var count = 0;
for (var i = START; i <= END; i++) {
    count++;
}
console.log(count); 

上面表示式輸出什麼?

乍一看是100,其實是干擾,這考察的不是迴圈,var變數啥的,而是JavaScript能表示的最大的數字是253,即次冪表示式Math.pow(2, 53)在這個最大數的基礎上加上一個整數得到的結果都是不準確的。看下面的例子:

    var END = Math.pow(2, 53);
    var START = END - 5;
    var count = 0;
    console.log(END);                   //      9007199254740992
    console.log(END + 7);               //      9007199254740997
    console.log(START++ <= END, START); // true 9007199254740988
    console.log(START++ <= END, START); // true 9007199254740989
    console.log(START++ <= END, START); // true 9007199254740990
    console.log(START++ <= END, START); // true 9007199254740991
    console.log(START++ <= END, START); // true 9007199254740992
    console.log(START++ <= END, START); // true 9007199254740992
    console.log(START++ <= END, START); // true 9007199254740992
    console.log(START++ <= END, START); // true 9007199254740992
    console.log(START++ <= END, START); // true 9007199254740992
    console.log(START++ <= END, START); // true 9007199254740992
    console.log(START++ <= END, START); // true 9007199254740992
    console.log(START++ <= END, START); // true 9007199254740992 

縮小了演示範圍,END已經是最大值9007199254740992,END+7,應該得到9007199254740998,其實是得到9007199254740997,這是一個錯誤的計算結果。如果在START的基礎上累加(每次加1),到第五次(包含第五次)得到的結果都是9007199254740992。所以在原題的i++過程中,執行到第100次之後每次得到的值都是9007199254740992,都滿足i<=END,也就是說這是一個死迴圈,程式一直執行,得不到任何結果。

7. 稀疏陣列問題

var ary = [0,1,2];
ary[10] = 10;
var result = ary.filter(function(x) { return x === undefined;});

這個表示式的結果是什麼?

這個問題考察的是稀疏陣列,稀疏陣列中的未賦值的元素是空,並且filter會忽略這些元素,所以上面filter語句返回的是空陣列[]。使用console.log()語句輸出稀疏陣列如下,可以看到console.log()語句也會忽略掉空位。

 

所以本題輸出的結果是[]。

 8. 數字精度問題

var two   = 0.2;
var one   = 0.1;
var eight = 0.8;
var six   = 0.6;
console.log([two - one === one, eight - six === two])

上面表示式輸出結果是什麼?

這個考察的是JavaScript數字精度問題,JavaScript的Number型別為雙精度IEEE 754 64位浮點型別,0.1不能精確的轉換成二進位制,會有溢位,遵循IEEE 754標準的語言都有這個毛病,包括java。這個問題造成0.1 + 1.2!= 0.3,0.8 - 0.6 != 0.2等等。這裡先這樣簡單解釋一下,下次專門寫一篇來解釋這個問題。

本題輸出結果是[true, false]。

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'));

上面的語句輸出什麼?

這個其實考察的是字面量和物件是否恆相等的問題,case語句是使用恆等(===)來判斷的,而‘A’ !== new Strting('A')返回false,所以最後輸出‘Do not know’。判斷恆等是三個等號'A' == new String('A')返回true,而兩個等號會呼叫物件的toString()方法,'A'==new String('A')返回true,如下圖:

10. String()函式

function showCase2(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!');
    }
}
showCase2(String('A')); 

上面語句輸出什麼?String("A")不會建立一個物件,呼叫String方法返回一個字串"A",所以輸出“Case A”。

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); 

上面的map結果是什麼?

這個題目是考察求餘運算子,前兩個數字7,4沒什麼好說的。‘13’%2,會把字串轉換成整形參與計算,得到1;-9%2會保留負號得到-1,符號是和第一個運算元保持一致,第二個運算元的符號會忽略,所以返回false,Infinity參與計求餘計算得到NaN。所以最終結果是[true, true, true, false, false]。

12. parseInt

console.log(parseInt(3, 8));
console.log(parseInt(3, 2));
console.log(parseInt(3, 0)); 

上面表示式分別輸出什麼?

這裡和第一題一樣,再一次考察parseInt這個函式,把題目翻譯翻譯是問:把8進位制裡的3轉化成10進位制整數是3;把2進位制中的3轉換成10進位制整形得到NaN,因為2進位制中沒有3;把10進位制中的3轉換成10進位制整數還是3;所以最終結果是[3, NaN, 3]。

13. Array.prototype

console.log(Array.isArray(Array.prototype)); 

上面表示式輸出結果是什麼?

這個是是一個非常容易忽略的知識點Array.property,是Array函式的原型物件,還是一個陣列,從下面的語句可以看出:

  

 所以本題的答案是true。

14. if語句

var a = [0];
if ([0]) {
  console.log(a == true);
} else {
  console.log("wut");
} 

上面這個語句得到什麼?

在JavaScript中if語句比較特殊,引用MDN中的介紹:

任何一個值,只要它不是 undefinednull、 0NaN或空字串(""),那麼無論是任何物件,即使是值為假的Boolean物件,在條件語句中都為真。

這個特性給我們寫if語句帶來很大的便利,不需要考慮if語句中的變數型別,因為只要它不是上述的幾種“沒有意義”的值,判斷都能通過,都能執行if語句。

但是非嚴格相等就不是這回事了,用==比較一個陣列和true肯定得到false,所以本題結果是輸出false。

15. 物件比較問題

[]==[] 

上面語句的輸出結果是什麼?

這個考察的是物件比較問題,[]是一個空陣列,陣列屬於物件,物件比較無論如何比較都不相等。如下圖:

 

 上面比較物件(複雜資料)得到的結果都是false,所以本題結果是false。

16. +是字串連線符也是加法運算

console.log('5' + 3);
console.log('5' - 3); 

加號遇到字串的時候就是字串連線符,會呼叫toString方法把另一個非字串轉換成字串,然後來連線,所以第一個結果是字串‘53’;第二個是減號,它的行為和加號剛好相反,它是把字串轉換成整形,第二個輸出2。

所以本題輸出['53', 2]。

17. 加減運算和正負運算

console.log(1 + - + + + - + 1); 

上面表示式輸出什麼結果?

這個要搞明白這個表示式其實是1 + (-+++-+1),除了第一個+是加法,後面的+,-都是正負運算,根據正正得正,正負得負,負負得正的原則-+++-+1是1,所以最後結果得到2。

18. 還是稀疏陣列

var ary = Array(3);
ary[0]=2
let s = ary.map(function(elem) { return '1'; });
console.log(s); 

上面語句輸出什麼?

這又是考察稀疏陣列問題,map會忽略調稀疏陣列中的空元素,輸出結果是["1", empty × 2]。

19. argument物件

function sidEffecting(ary) {
    arguments[1] = 10;
    ary[0] = ary[2];
}
function bar(a,b,c) {
    c = 10
    sidEffecting(arguments);
    return a + b + c;
}
console.log(bar(1,1,1)) 

上面語句輸出什麼?

這一題考察的是對argument物件的瞭解,argument是一個類陣列物件,修改物件的屬性值會影響其他使用到物件的地方,即使變數不在同一範圍內。再加上物件屬性可以使用類似陣列下標的方式來訪問,物件做了字串到值的對映,而陣列做的是數字到值的對映。

根據這些可以推導結果是21。

20. JavaScript中的最大數

var a = 111111111111111110000,
      b = 1111;
console.log(a + b); 

上面的語句輸出什麼內容?

這個又一次考察JavaScript中的最大值問題,JavaScript中最大值是Math.pow(2, 53)=9007199254740992,計算過程中如果超出這個最大值範圍就不準確了,但是怎麼個不準確法,是不確定的。這裡輸出結果是111111111111111110000, 還是a的值。

21. Array.property.reverse

var x = [].reverse;
x(); 

上面語句輸出什麼?

這個有些奇怪了,原文中解釋說reverse方法會返回撥用這個方法的陣列本身(就是this),但是x()沒有呼叫者,所以this指向了全域性物件window。但是我在chrome中試過,這個是會報錯的,報錯資訊是:Uncaught TypeError: Cannot convert undefined or null to object at reverse (<anonymous>)。這個可能是原文寫的比較早,後面的瀏覽器修改了這個行為。

22. Number.MIN_VALUE

console.log(Number.MIN_VALUE > 0); 

這個考察Number.MIIN_VALUE的值,MDN上解釋如下:

Number.MIN_VALUE表示最小正數,即最接近 0 的正數 (實際上不會變成 0)。最大的負數是 -MIN_VALUE

所以Number.MIN_VALUE是大於0的,這裡輸出是true。

23. 強制轉換

console.log([1 < 2 < 3, 3 < 2 < 1]); 

上面表示式輸出什麼?

這裡考察的是在大於號,小於號運算中true會被強制轉換為1,false會被強制轉換成0。相當於console.log([true < 3, false < 1]),轉換後就是console.log([1 < 3, 0 < 1]),所以這裡輸出結果是[true, true]。

24. 陣列字面量的字串表示

console.log(2 == [[[2]]]); 

上面語句輸出什麼?

這個題套路深,我先說答案,是true。連原文作者都驚歎這是什麼鬼?原文是:the most classic wtf!如果試著解釋的話,非嚴格相等==在遇到字面量[[[2]]]的時候,會檢視將它轉換成字串然後和2比較,但是[[[2]]].toString()返回‘2’,然後‘2’ == 2返回true,是不是很驚喜?是不是想罵人?如下圖,這樣非嚴格相等就返回true了。

 

25. 3.和.3

console.log(3.toString());
console.log(3..toString());
console.log(3...toString()); 

上面語句輸出什麼結果?

這裡要搞清楚3.和.3都是合法的數字3.是一個省略了尾數部分0的數字,.3是一個省略了整數部分0的數字;第一句中但是toString()不是一個數字,所以第一句報錯:Uncaught SyntaxError: Invalid or unexpected token。第二句相當於(3.).toString()輸出”3“。第三句和第一句一樣報錯,原因也是一樣的,第二個.後面的一串..toString()不是一個合法的數字,於是就報錯了。

所以正確的答案是error,”3“,error。

26. var和閉包問題

(function(){
    var x = y = 1;
})();
console.log(y);
console.log(x); 

上面的語句輸出什麼?

這一題考察的是對閉包和var變數的瞭解。閉包有隔離變數的作用,所以var不能提升變數,在閉包外部訪問x是失敗的,所以第二句輸出Undefined。閉包中如果不使用var宣告變數,直接不帶var,這樣申明y=1,反而y是全域性的,在外部是可以訪問到變數y的,所以第一句輸出1。是不是很驚喜?

27. 正規表示式不可相互比較

var a = /123/, b = /123/;
console.log(a == b);
console.log(a === b); 

上面兩句分別輸出什麼?

這個考察的是正規表示式比較問題,雖然字面量內容相同,但是JavaScript認為這是兩個正規表示式,是物件型別,他們是不相等的。這裡輸出結果為false false。

28. 陣列比較

    var a = [1, 2, 3], b = [1, 2, 3], c = [1, 2, 4];
    console.log(a ==  b);
    console.log(a === b);
    console.log(a > c);
    console.log(a < c); 

上面比較以此輸出什麼?

即使陣列字面量相等,使用==或者===判斷兩個陣列也不相等。而大於,小於比較是按照字典順序比較的,這裡以a<c具體如下:

  • 比較a[0]<c[0],相等,繼續比較下一位
  • 比較a[1]<c[1],相等,繼續比較下一位
  • 比較a[2]<c[2],返回true,於是a<c返回true

所以上面以此輸出false,false,false,true。

29. 建構函式的原型

var a = {}, b = Object.prototype
console.log([a.prototype === b, Object.getPrototypeOf(a) === b]); 

上面程式碼輸出什麼結果?

JavaScript中函式才有prototype屬性,物件是沒有的,物件有__proto__,指向物件的建構函式的原型,所以a.prototype是undefined。b是Object函式的原型,是一個物件,所以第一個是false。第二個是使用getPrototypeOf方法獲取物件a的原型(即a.__proto__),這個和Object.prototype相等的,所以第二個表示式返回true。

a.__proto__是物件的原型,它是瀏覽器暴露出來的屬性,JavaScript標準中沒有這個屬性。下面的語句輸出[true]:

var a = {}, b = Object.prototype
console.log([a.__proto__ === b); 

如下圖:

 

30. 函式的原型物件

function f() {}
var a = f.prototype, b = Object.getPrototypeOf(f);
console.log(a === b); 

上面的語句輸出什麼?

這還是考察函式原型問題。這f.prototype是獲取函式f的原型物件,Object.getPrototypeOf(f)是獲取物件建構函式的原型,注意函式也是物件,它的建構函式是Function(),因此f的建構函式的原型是Function.property,因此這這裡輸出false。

31. Function.name

function foo() { }
var oldName = foo.name;
foo.name = "bar";
console.log([oldName, foo.name]); 

上面的語句輸出什麼結果?

這道題考察的是函式的name屬性,function.name 屬性返回函式例項的名稱,這個屬性是不可寫的,因為訪問器屬性中writable屬性為false。所以這一題輸出的是['foo', 'foo']。

32. str.replace

console.log("1 2 3".replace(/\d/g, parseInt)); 

上面語句輸出什麼內容?這一題考察的是replace方法,str.replace方法的原型如下:

str.replace(regexp|substr, newSubStr|function

引數解釋如下:

  • regexp (pattern):一個正規表示式物件(即RegExp物件)或者其字面量。該正則所匹配的內容會被第二個引數的返回值替換掉。它和substr是二選一的。
  • substr (pattern):一個將被newSubStr替換的字串。其被視為一整個字串,而不是一個正規表示式。僅第一個匹配項會被替換。它和regexp是二選一的。
  • newSubStr (replacement):用於替換掉第一個引數在原字串中的匹配部分的字串。該字串中可以內插一些特殊的變數名。參考下面的使用字串作為引數。它和function是二選一的。
    變數名 代表的值
    $$ 插入一個 "$"。
    $& 插入匹配的子串。
    $` 插入當前匹配的子串左邊的內容。
    $' 插入當前匹配的子串右邊的內容。
    $n 假如第一個引數是 RegExp物件,並且 n 是個小於100的非負整數,那麼插入第 n 個括號匹配的字串。提示:索引是從1開始
  • function (replacement):一個用來建立新子字串的函式,該函式的返回值將替換掉第一個引數匹配到的結果。參考下面的指定一個函式作為引數。它和newSubStr是二選一的。
    變數名 代表的值
    match 匹配的子串。(對應於上述的$&。)
    p1,p2, ... 假如replace()方法的第一個引數是一個RegExp 物件,則代表第n個括號匹配的字串。(對應於上述的$1,$2等。)例如,如果是用 /(\a+)(\b+)/ 這個來匹配,p1 就是匹配的 \a+,p2 就是匹配的 \b+。
    offset 匹配到的子字串在原字串中的偏移量。(比如,如果原字串是 'abcd',匹配到的子字串是 'bc',那麼這個引數將會是 1)
    string 被匹配的原字串。
    NamedCaptureGroup 命名捕獲組匹配的物件

不得不說replace方法有點複雜,在這個例子中第一個引數是一個正規表示式,第二個引數是一個函式,函式引數以此是match,offset,string,NamedCaptureGroup,但是parseInt僅僅接受2個引數,所以相當於執行下面的語句:

parseInt('1', 0) // 10進位制裡的1是1
parseInt('2', 2) // 2進位制裡沒有2,所以NaN
parseInt('3', 4) // 4進位制中的3是3

所以最終結果是"1 NaN 3",這個作者很喜歡拿parseInt說事。

33. eval函式

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));

上面的語句輸出什麼?

第一句f.name就是”f“;parent是Function.prototype,這是一個物件,它沒有name屬性,所以第二句應該是啥都不輸出;eval()函式會將傳入的字串當做 JavaScript 程式碼進行執行,eval(f.name)相當於eval('f'),執行結果是輸出函式f的內容,type of計算返回function;最後一句返回空;

34. exp.test

var lowerCaseOnly =  /^[a-z]+$/;
console.log([lowerCaseOnly.test(null), lowerCaseOnly.test()]); 

上面語句輸出什麼內容?

RegExp.prototype.test()方法的引數是一個字串,如果不是字串會嘗試轉換成字串。所以上面兩句相當於lowerCaseOnly.test("null"),lowerCaseOnly.test(”Undefined“),所以返回[true, true]

35. 陣列元素最後一個逗號

console.log([,,,].join(", "));

上面的表示式輸出什麼內容?

我們定義一個有三個元素的陣列的時候可以這樣var arr = [1, 2, 3]; 也可以這樣var arr = [1, 2, 3,];它後面了一個逗號,但是還是三個元素。本題的關鍵點就是[, , ,],這其實是一個有三個空元素的陣列,輸出 [empty × 3]。三個空元素用逗號連線起來最後輸出是兩個逗號,因為都是空元素,其實最後一個逗號後面是有一個空元素的。所以本題輸出",,"。如下圖:

 

其實定義一個物件也可以在最後一個屬性後面加上一個逗號,例如 var obj = { name: 'zhangsan', age: 20, };

36. class關鍵字

var a = {class: "Animal", name: 'Fido'};
console.log(a.class); 

上面的語句輸出什麼?

這一題是考察class關鍵字,我在chrome裡輸出的是正確的值“Animal”,記住在定義變數或者屬性名字的時候儘量避免JavaScript關鍵字。如果屬性裡有關鍵字,可以這樣使用a["class"]。

37. 時間轉換問題

var a = new Date("epoch")
console.log(a); 

上面的表示式輸出什麼?

new Date()建構函式傳入的必須是一個時間字串,即可以通過Date.parse()解析成功。所以本題輸出Invalid Date。

38. 函式的形參個數

var a = Function.length,
    b = new Function().length
console.log(a === b); 

上面表示式輸出什麼?

Function 構造器本身也是個Function。他的 ength屬性值為 1,所以a=1。該屬性 Writable: false, Enumerable: false, Configurable: true。但是new Function()是一個物件,他沒有形參,說以b=0,本題輸出false。另外函式的實參的個數可以用argument.length獲取。

39. 還是時間轉換問題

var a = Date(0);
var b = new Date(0);
var c = new Date();
console.log([a === b, b === c, a === c]); 

上面的語句輸出什麼?

直接呼叫函式Date(),得到的結果是一個表示時間的字串;使用new操作符+建構函式,得到的是一個時間物件;引數是0返回的是格林威治0時,不帶引數返回的是當前時間;搞清楚這些之後這題就簡單了,a是一個時間字串,b是一個時間物件,表示格林威治0時,c也是一個時間物件,不過是當前時間。所以本題輸出[false, false, false]

40. Math.max()&Math.min()

var min = Math.min(), max = Math.max()
console.log(min < max); 

上面語句輸出什麼?

注意Max.max()和Max.min()傳入的引數是一個陣列,如果不傳參,前者返回+Infinity,後者返回-Infinity。Infinity不能做比較,所以本題返回false。

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=2"),
    a4 = captureOne(wordRe,  "WORD=2");
console.log([a1 === a2, a3 === a4]);

上面表示式輸出什麼?這題考察的是正規表示式的/g選項,這個選項表示全域性匹配;找到所有匹配,而不是在第一個匹配後停止。來看下面的例子:

var myRe = /ab*/g;
var str = 'abbcdefabh';
console.log(myRe.exec(str));
console.log(myRe.exec(str)); 

輸出結果如下:

 

 

從結果可以看出,執行兩次都有返回結果,分別找到字串中兩處符合條件的匹配。

而原題中第一個正在有/g選項,第一次匹配成功之後會從當前位置往後查詢,所以在執行a3 = captureOne(numRe,  "NUM=2")的時候不是從字串位置0開始查詢的,而是從第5位開始,所有匹配就失敗了,找不出1。

所以本題輸出[true, false]。

42. Date中的month

var a = new Date("2014-03-19"),
    b = new Date(2014, 3, 19);
console.log([a.getDate() === b.getDate(), a.getMonth() === b.getMonth()]); 

上面表示式輸出什麼?這個問題考察的是Date物件中的月份問題,注意使用第二種方式定義時間物件,

new Date(year, monthIndex [, day [, hours [, minutes [, seconds [, milliseconds]]]]]); 

第二個引數是monthIndex,它是從0開始的,3表示4月份,所以本題輸出[false, false]。如果初學JavaScript,感覺套路深啊套路深。

43. 正規表示式中的轉義

if ('http://giftwrapped.com/picture.jpg'.match('.gif')) {
   console.log('a gif file');
} else {
    console.log('not a gif file');
} 

String.prototype.match 接受一個正則, 如果不是, 按照 new RegExp(obj) 轉化. 所以 . 並不會轉義,所以開頭的‘http://gifwrapped......’中/gif就匹配了 /.gif/,所以輸出“a gif file”

44. 變數提升

function foo(a) {
    var a;
    return a;
}
function bar(a) {
    var a = 'bye';
    return a;
}
console.log([foo('hello'), bar('hello')]); 

上面語句輸出什麼?a作為引數其實已經宣告瞭, 所以 var a; var a = 'bye' 其實就是 a; a ='bye',所以本題輸出["hello", "bye"]。

 

JavaScript語法本來就有很多不合理的地方,導致書寫JavaScript很容易出錯,本文中如有錯誤,歡迎各位看官提出來。

相關文章