詳解javaScript變態25題

好的JavaScript測試題目應該是:門外漢見了皺眉,行家見了疑惑題目是不是簡單了點,同時考察點覆蓋全面。//zxx: 我目前還沒有這個能耐設計出如此優秀的題目。

這裏要介紹的”Another JavaScript quiz“(by james)中的題目不是屬於變態題目,而是確實屬於變態題目,不過是表面上的,很多內容確實可能會遇到的。綜合評價下就是:面試價值不及格,學習價值是很讚的,因此,探討分享很有意義。

“Another JavaScript quiz“中有25個很簡潔的JavaScript測試題,全部都是考察返回值的,例如1 && 3的返回值是?

25道題目全部完成後,可以點擊下圖所示的按鈕,檢查你的正確率以及那些題目出錯了。

我自己快速測試了下,如果每題算4分的話,我的成績是56分,不及格,可見自己對JS的學習以及理解還有一段路要走。

您也可以做一下這些題目,完成之後,若有疑惑,可以參考我下面純個人的理解 – 部分理解可能不準確,歡迎指正。同時,網上應該有其他一些前輩的解釋,您也可以參考參考。

二、公子,不急,慢慢來嘛

(1) 1 && 3 //返回的是?
結果是3.
&&是幾乎無人不知,表示“與”。男方要娶水靈靈的妹子,七大姑八大婆都要同意,否則繼續鍛鍊右手吧。
上面1 && 3本質上等同於大姑媽 && 三姑媽。

從左往右,如果“大姑媽”通過,繼續“三姑媽”;否則直接返回“大姑媽”。因爲“三姑媽”後面沒有其他親戚了,因此直接返回“三姑媽”。

在JavaScript中, 1 == true // true,因此,1 && 3等同於“大姑媽”通過了,返回最後一個檢測的親戚“三姑媽”,也就是這裏的結果3.

因此,實際上,我們平時的 if (1 && 3) {} 等同於 if (3) {}.

如果這裏的題目換成0 && 3返回的是?那結果是如何呢?

因爲0 == false // true, 因此走不到“三姑媽”這一關,直接返回了0. 也就是if (0 && 3) {} 等同於 if (0) { /* 不會執行 */ }.

實用性
可以避免if嵌套。例如,要問頁面上某個dom綁定點擊事件,我們需要先判斷這個dom元素存不存在,我們可能會這樣做:
var dom = document.querySelector(“#dom”);
if (dom) { dom.addEventListener(“click”, function() {}); }

實際上,我們可以使用&&做一些簡化:
var dom = document.querySelector(“#dom”);
dom && dom.addEventListener(“click”, function() {});

(2) 1 && “foo” || 0 //返回的是?

很簡單的一道題,結果是”foo”.

這裏出現了一個新的關係符||, 表示“或”的意思。男方要娶乾巴巴的妹子,七大姑八大婆只要一個說好就可以了。比方說1 || 3表示:如果大姑媽說可以,則成了,返回大姑媽;如果大姑媽不允許,再看看三姑媽的意思。

因此,if (1 || 3) {}實際等同於if (1) {}; if (0 || 3) {} 等同於if (3) {}.

實用性
||可以讓我們使用一種更快捷簡易的方式爲參數添加默認值。例如,寫jQuery插件的時候,可選參數是可以缺省的,此時實際上值爲undefined,會讓後面的參數extend產生困擾。因此,我們會經常見到類似這樣的代碼:
$.fn.plugin = function(options) {
options = options || {};
// …
};

顯然,根據上面的理解,1 && “foo”返回的是”foo”, 而”foo” || 0, 因”foo” == true爲true, 自然返回是“大姑媽” – “foo”. 這就是最終結果”foo”的由來。

(3) 1 || “foo” && 0 //返回的是?

結果是1.
據說邏輯與運算符(&&)優先級要大於邏輯或(||)。如果是這樣,那這裏的結果返回應該是這樣子的:1 || “foo” && 0 → 1 || 0 → 1.

(4) (1, 2, 3) //返回的是?

結果是3.
這裏考察的是逗號運算符,也稱多重求值。逗號運算符據說是優先級最低的運算符。
一般表現形式是:大姑媽,二姑媽,三姑媽,……N姑媽。
運算規則如下:折騰大姑媽,折騰二姑媽,折騰三姑媽,……折騰N姑媽,最後返回的N姑媽的返回值。

因此,這裏1, 2, 3就是折騰了1,折騰了2,折騰了3並且返回了3的返回值。因此,結果是3.

好,現在提問:alert(1, 2, 3);的彈出值是?
結果是3……………………是不可能的!正確結果是1.

不要困擾。原因很簡單,alert身後的()實際上也是一種運算符,這裏指函數調用,優先級相當的高。這裏,1, 2, 3已經被奴役成alert方法(彈出第一個參數值)的參數了,因此,彈出的是1.
如果是alert((1, 2, 3));則彈出的就是3了。

實用性
舉個很簡單的例子,i, j兩個變量同時遞增,可能我們會這樣寫:
var i=1;
var j=1;
i+=1;
j+=2;

我們可以藉助逗號運算符獲得更好地閱讀體驗,至於性能是否有提高,我個人並不清楚,就算有,差異也是可以忽略的。
var i=1, j=1;
i+=1, j+=2;

(5) x = { shift: [].shift };
x.shift();
x.length; //返回的是?

結果是IE6/IE7 undefined, 其他瀏覽器0. 不過,個人觀點,IE6/IE7其實並不屬於這個題目的考察瀏覽器。因此,我們着重討論爲何IE8+等瀏覽器爲何結果是0.

首先,我們瞭解下數組的shift方法。 我個人一般把pop/push歸爲一對基友,移除尾部元素和尾部添加元素,都是做掃尾工作的。shift/unshift當作另外一對基友,移除頭部元素和頭部添加元素,都是在頭部打點的。記住,單詞字符個數少的(pop, shift)都是做刪除的,這樣記憶就不會混淆了。

從外表上看,shift方法移除數組中的第一個元素並返回這個元素。如果這個數組是個空數組,則返回undefined. 一般而言,shift操作會改變後面所有數組項在內存中的地址,因此,相比pop方法要慢得多。

從內在來看,shift還能給對象自動添加length屬性。Mozilla開發者中心(MDC)Mozilla開發者網絡(MDN – Mozilla Developer Network)的解釋是這樣子的:

shift is intentionally generic; this method can be called or applied to objects resembling arrays. Objects which do not contain a length property reflecting the last in a series of consecutive, zero-based numerical properties may not behave in any meaningful manner.

簡體中文表示就是(這裏的釋義自己只有80%確認,若有不準確,請極力指證):
shift可以有意泛化(變身成鴨子);該方法可以被類數組對象call或者apply. 對象如果沒有length屬性,可能會以無意義的方式在最後反射一系列連續,基於0的數值屬性。

什麼意思呢?我們看下面這個更容易理解的問題:
x = {};
[].shift.call(x);
x.length; //返回的是?

結果是0. //zxx: IE6/IE7 undefined.

x對象原本木有length屬性,在被數組shift方法call後,添加了一個值爲0的length屬性。此爲數組shift方法的泛化性,專業術語爲泛型(generic)。其基友方法,例如pop, push等都是如此。

OK, 回到原題,實際上,x.shift()的調用等同於[].shift.call(x), 不明白?看下面的一步一步分析。
var x = {
shift: function() {
console.log(this === x); // true
}
};
x.shift();

因此,在
x = { shift: [].shift }
條件下,
x.shift執行的就是[].shift的執行,只不過,[].shift()函數中的this上下文就是x(因爲this===x),就等同於直接的[].shift.call(x)調用。

這條題目中x對象的shift屬性名實際上是用來干擾,提高解答難度的刻意命名。我們使用其他命名,結果也是一樣的。
x = { shit: [].shift };
x.shit();
x.length; // 0

(6) {foo:1}[0] //返回的是?
結果是[0], 或者這種表現形式0 { 0 : 0 } – 來自IE控制檯.

不要試圖使用alert或者控制檯console.log輸出,這隻會返回不一樣的結果undefined,哦?爲何會有這等差異?
出題者james在”Labelled blocks, useful?“中有這樣的解釋:
Since JavaScript doesn’t have block scope, using a block anywhere other than in the conventional places (if/while etc.) is almost totally pointless. However, as I mentioned, we could use them to annotate and contain logically related pieces of code…

意思是說:
因爲JavaScript沒有塊作用域,所以,如果語句塊不是常規使用,如if/while等,其幾乎就是打醬油的。甚至,我們可以利用這個特性註釋或者包含相關的邏輯片段代碼…

我們有必要好好理解這裏“打醬油的”意思,這裏的“打醬油”並不是指{}塊中語句是打醬油,而是其本身就是個醬油。嘛意思,實例說明一切:
2 // 返回值爲2
{2} // 返回值爲2
str = “string” // 返回值爲string
{ str = “string” } // 返回值爲string
foo: 1 // 返回值爲1
{ foo: 1 } // 返回值爲1

也就是說花括號幾乎就是皇帝的新衣。因此,這裏的答案就不難理解了,{foo:1}[0]實際上就是foo:1; [0]. 返回的就是[0]本身。
注意這裏反覆出現的措辭“幾乎”。“幾乎醬油”的潛臺詞是有時候還能頂個臭皮匠。james舉了個在塊中使用break語句的例子,如下:
var x = 1;
foo: {
x = 2;
break foo;
x = 3;
}
x === 2; // true

非這種情況的break/continue只能在switch語句以及循環中使用。很有意思吧,我反正是學習了!

(7) [true, false][+true, +false] //返回的是?

結果是true.
首先,我們瞭解下+true和+false是個什麼東西。

想必都清楚+1與-1是什麼東東。指的是正數1與負數1. 同時,稍微對JS有了解的人也清楚,true == 1, false == 0. 因此,實際上,[+true, +false]就是[+1, +0].

[true, false]爲數組,後面的[+true, +false]實際爲索引,然而索引只需要一個值,因此,[+true, +false]返回的實際是我們上面提到的逗號運算——返回最後一個值,也就是+0, 也就是0.

因此,本題的問題其實是:[true, false][0] //返回的是?
旺財估計都已經明白了,就不多說了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章