昨天在著名前端架構師Baranovskiy的部落格中看到一個文章《So, you think you know JavaScript?》
題目一:
if (!("a" in window)) { var a = 1;}alert(a);
題目二:
var a = 1, b = function a(x) { x && a(--x); };alert(a);
題目三:
function a(x) { return x * 2;}var a;alert(a);
題目四:
function b(x, y, a) { arguments[2] = 10; alert(a);}b(1, 2, 3);
題目五:
function a() { alert(this);}a.call(null);
請不要藉助任何協助工具,心算答案。答案在下面。
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
答案:
題目1
if (!("a" in window)) { var a = 1;}alert(a);
代碼含義:如果window不包含屬性a,就聲明一個變數a,然後賦值為1。
你可能認為alert出來的結果是1,然後實際結果是“undefined”。要瞭解為什麼,需要知道JavaScript裡的3個概念。
首先,所有的全域變數都是window的屬性,語句 var a = 1;等價於window.a = 1; 你可以用如下方式來檢測全域變數是否聲明:
"變數名稱" in window
第二,所有的變數聲明都在範圍範圍的頂部,看一下相似的例子:
alert("a" in window);var a;
此時,儘管聲明是在alert之後,alert彈出的依然是true,這是因為JavaScript引擎首先會掃墓所有的變數聲明,然後將這些變數聲明移動到頂部,最終的代碼效果是這樣的:
var a;alert("a" in window);
這樣看起來就很容易解釋為什麼alert結果是true了。
第三,你需要理解該題目的意思是,變數聲明被提前了,但變數賦值沒有,因為這行程式碼封裝括了變數聲明和變數賦值。
你可以將語句拆分為如下代碼:
var a; //聲明a = 1; //初始化賦值
當變數聲明和賦值在一起用的時候,JavaScript引擎會自動將它分為兩部以便將變數聲明提前,不將賦值的步驟提前是因為他有可能影響代碼執行出不可預期的結果。
所以,知道了這些概念以後,重新回頭看一下題目的代碼,其實就等價於:
var a;if (!("a" in window)) { a = 1;}alert(a);
這樣,題目的意思就非常清楚了:首先聲明a,然後判斷a是否在存在,如果不存在就賦值為1,很明顯a永遠在window裡存在,這個指派陳述式永遠不會執行,所以結果是undefined。
提前這個詞語顯得有點迷惑了,你可以理解為:先行編譯。
題目2
var a = 1, b = function a(x) { x && a(--x); };alert(a);
這個題目看起來比實際複雜,alert的結果是1;這裡依然有3個重要的概念需要我們知道。
首先,在題目1裡我們知道了變數聲明在進入執行內容就完成了;第二個概念就是函式宣告也是提前的,所有的函式宣告都在執行代碼之前都已經完成了聲明,和變
量聲明一樣。澄清一下,函式宣告是如下這樣的代碼:
function functionName(arg1, arg2){ //函數體}
如下不是函數,而是函數運算式,相當於變數賦值:
var functionName = function(arg1, arg2){ //函數體};
澄清一下,函數運算式沒有提前,就相當於平時的變數賦值。
第三需要知道的是,函式宣告會覆蓋變數聲明,但不會覆蓋變數賦值,為瞭解釋這個,我們來看一個例子:
function value(){ return 1;}var value;alert(typeof value); //"function"
儘快變數聲明在下面定義,但是變數value依然是function,也就是說這種情況下,函式宣告的優先順序高於變數聲明的優先順序,但如果該變數value賦值了,那結果就完全不一樣了:
function value(){ return 1;}var value = 1;alert(typeof value); //"number"
該value賦值以後,變數賦值初始化就覆蓋了函式宣告。
重新回到題目,這個函數其實是一個有名函數運算式,函數運算式不像函式宣告一樣可以覆蓋變數聲明,但你可以注意到,變數b是包含了該函數運算式,而該函數運算式的名字是a;不同的瀏覽器對a這個名詞處理有點不一樣,在IE裡,會將a認為函式宣告,所以它被變數初始化覆蓋了,就是說如果調用a(–x)的話就會出錯,而其它瀏覽器在允許在函數內部調用a(–x),因為這時候a在函數外面依然是數字。基本上,IE裡調用b(2)的時候會出錯,但其它瀏覽器則返回undefined。
理解上述內容之後,該題目換成一個更準確和更容易理解的代碼應該像這樣:
var a = 1, b = function(x) { x && b(--x); };alert(a);
這樣的話,就很清晰地知道為什麼alert的總是1了。
題目3
function a() { return 1 ;}var a;alert(a);
這個題目比較簡單:即函式宣告和變數聲明的關係和影響,遇到同名的函式宣告,不會重新定義
題目4
function b(x, y, a) { arguments[2] = 10; alert(a);}b(1, 2, 3);
關於這個題目,ECMAsCRIPT 262-3的規範有解釋的。
使用中的物件是在進入函數上下文時刻被建立的,它通過函數的arguments屬性初始化。arguments屬性的值是Arguments對象.
關於 Arguments對象的具體定義,看這裡:ECMAScript arguments 對象
題目5
function a() { alert(this);}a.call(null);
這個題目可以說是最簡單的,也是最詭異的!關於這個題目,我們先來瞭解2個概念。
這個問題主要考察 Javascript 的 this 關鍵字,具體看這裡:
關於Javascript語言中this關鍵字的用法
關於 a.call(null); 根據ECMAScript262規範規定:如果第一個參數傳入的對象調用者是null或者undefined的話,call方法將把全域對象(也就是window)作為this的值。所以,不管你什麼時候傳入null,其this都是全域對象window,所以該題目可以理解成如下代碼:
function a() { alert(this);}a.call(window);
所以彈出的結果是[object Window]就很容易理解了。
—————
總結:
這5個題目雖然貌似有點偏,但實際上考察的依然是基本概念,只有熟知了這些基本概念才能寫出高品質代碼。
大致就這些,拋磚引玉。