Dmitry Baranovskiy是何許人也?他是目前世界最優秀的JS開源圖形庫Raphaël的作者,還做了許多JS遊戲自娛,是JS界頂尖高手之一。
以下五道題是放於他的部落格上,直到另一個它們被另一個JS高手Nicholas C. Zakas(Yahoo!首頁首席前端工程師)提到,才迅速在網路蔓延開去。大家先試著自己做一下,想不明白才看解析吧。
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);
第一題(請經過思考後再按運行框)
<br /><!doctype html><br /><html dir="ltr" lang="zh-CN"><br /> <head><br /> <meta charset="utf-8"/><br /> <title>Dmitry Baranovskiy的javascript謎題 by司徒正美</title></p><p> <script type="text/javascript" charset="utf-8"></p><p>if (!("a" in window)) {<br /> var a = 1;<br />}<br />alert(a);</p><p> </script></p><p> </head><br /> <body></p><p> </body><br /></html><br />
運行代碼
第二題(請經過思考後再按運行框)
<br /><!doctype html><br /><html dir="ltr" lang="zh-CN"><br /> <head><br /> <meta charset="utf-8"/><br /> <title>Dmitry Baranovskiy的javascript謎題 by司徒正美</title></p><p> <script type="text/javascript" charset="utf-8"></p><p> var a = 1,<br /> b = function a(x) {<br /> x && a(--x);<br /> };<br /> alert(a);</p><p> </script></p><p> </head><br /> <body></p><p> </body><br /></html><br />
運行代碼
第三題(請經過思考後再按運行框)
<br /><!doctype html><br /><html dir="ltr" lang="zh-CN"><br /> <head><br /> <meta charset="utf-8"/><br /> <title>Dmitry Baranovskiy的javascript謎題 by司徒正美</title></p><p> <script type="text/javascript" charset="utf-8"></p><p>function a(x) {<br /> return x * 2;<br />}<br />var a;<br />alert(a);</p><p> </script></p><p> </head><br /> <body></p><p> </body><br /></html><br />
運行代碼
第四題(請經過思考後再按運行框)
<br /><!doctype html><br /><html dir="ltr" lang="zh-CN"><br /> <head><br /> <meta charset="utf-8"/><br /> <title>Dmitry Baranovskiy的javascript謎題 by司徒正美</title></p><p> <script type="text/javascript" charset="utf-8"></p><p> function b(x, y, a) {<br /> arguments[2] = 10;<br /> alert(a);<br /> }<br /> b(1, 2, 3);</p><p> </script></p><p> </head><br /> <body></p><p> </body><br /></html><br />
運行代碼
第五題(請經過思考後再按運行框)
<br /><!doctype html><br /><html dir="ltr" lang="zh-CN"><br /> <head><br /> <meta charset="utf-8"/><br /> <title>Dmitry Baranovskiy的javascript謎題 by司徒正美</title></p><p> <script type="text/javascript" charset="utf-8"></p><p>function a() {<br /> alert(this);<br />}<br />a.call(null);</p><p> </script></p><p> </head><br /> <body></p><p> </body><br /></html><br />
運行代碼
解釋
//第一題//undefined//★★考察javascript在先行編譯期幹了什麼javascript分兩個階段,先行編譯期與運行期先行編譯期,var 變數提前,術語為提前聲明。這時javascript引擎是從上到下,從外到內整塊地分析我們的源碼,構建文法樹。 if (!("a" in window)) { var a = 1; } alert(a);由於javascript不存在塊範圍,因此if裡面的東西與if外面屬於同一個範圍。在先行編譯階段,var a被抽取出來,放到範圍的頂部。更專業的解釋可見ecma262r3,var 變數被放置到一個叫調用對象的東東中。那麼這時,我們的代碼就變成這樣 var a if(!("a" in window)){ a = 1 } alert(a)到運行期時,便開始從上到下,從裡到外執行那些修改過的指令碼。很明顯,"a" in window 為true ,再加個否定就為false,進不到if語句裡面了,當然alert undefined(凡是只聲明沒賦值的都會被自動賦上window的一個屬性undefined)//第二題 // 1//★★考察函式宣告,函數運算式與命名函數運算式這是函式宣告:function aa(){ alert("司徒正美!")}這是函數運算式:var bb = function(){ alert("司徒正美!!")}那這個算什嗎? var cc = function dd(){ alert("司徒正美!!!") }簡單,也是函數運算式,不過是叫命名函數運算式。但這東西並不統一,先說標準瀏覽器下,dd這個函數名只對其函數體內可見,因此外圍範圍一調用它就會出錯,因為它並不存在此引用(或叫變數吧)。這特性好像比較新的標準瀏覽器支援,如FF3+,safari3+(safari2有bug)。再回頭看IE,IE是支援這東西,還支援更多奇怪的寫法,如http://bbs.51js.com/viewthread.php?tid=86272&highlight=%2Binfinte但IE的命名函數運算式很顯然與標準的出入太大了。首先dd這函數名對函數的內外範圍是可見的,這很要命,很容易汙染全域範圍,造成的命名衝突,其次,它會建立兩個對象,cc是一個,dd是一個,修改其中一個會不會同步更新另一個,是記憶體流失的根源之一。命名函數運算式在IE中也作為函式宣告,換言之它會在先行編譯階階就會幹掉我們的同名函式宣告,你怎樣調試也打不出原因,因為調試是在運行期啟動並執行……嘛,由於這個alert是位於外圍範圍,因此在標準瀏覽器,我們的代碼可以看作是這樣: var a = 1; var b = function (x) { x && a(--x); }; alert(a);因此是1在IE中,先行編譯階段,其實有兩個a,不過沒有關係,反正前面的會覆蓋後面的,然後到運行期,a會賦予1,這時它不會賦給另一個函數對象,因為另一個a已在先行編譯階段就被幹掉了。換言之,這次很幸運避免了建立兩個函數對象,於是alert(1)。但命名函數運算式在IE下的糟糕表現,我們還是避免使用它吧,要想在內部調用自身,arguments.callee完成能勝任。//第三題//彈出函數的toString(),即//function a(x) {// return x * 2;//}思路基本同第一題,var變數聲明在先行編譯階段被提前了,然後被重寫。var a;function a(x) { return x * 2;}alert(a);//第四題//10//★★考察arguments對象在執行一個函數前,它會把它自身(callee),它的運行環境(callee.caller),傳入參數的個數,構建成一個argument對象,接著就像數組一樣,把參數一個個塞進argument中。運行時,如果某個參數被修改了,arguments對應的值也同步更新。理解這些就簡單了。原函數的意思是傳入三個參數,內部會把第三個參數修改成10,然後彈出第三個參數10,因此它根本不在乎你傳什麼,只要傳夠三個或三個以上參數,它就會alert 10。//第五題//彈出window對象的toString()//基本上不是[object Window] 就是[object],[object DOMWindow]//★★考察動態綁定的用法,詳見我的博文《javascript的動態this與動態綁定》function a() { alert(this);//這裡將返回其調用者}如果現在直接運行,肯定返回全域對象window。但如果使用apply或call來改變調用對象呢,原則上其第一個參數就是調用對象,通常也被稱之為thisObject。但有幾個特殊情況,當第一參數為null,undefined,或乾脆一個參數也沒有,就會預設為是傳入window對象。
補充實驗:
<br /> var cc = function dd(){<br /> alert("司徒正美!!!")<br /> }</p><p> alert(cc)<br /> alert(dd) //注意IE與標準的區別<br />
運行代碼
嘛,解釋就完全按我的理解去寫啦,應該大體上和Nicholas C. Zakas差不多。可能他的更詳細,畢竟從原文篇幅來看也確實如此,英文好的就看英文吧。本時我搜到一些俄羅斯文,法文什麼的,也基本上貼在google線上翻譯上,連蒙帶猜地學習。外國人的技術比我們強多了,大家應該多上外國網站。