通過翻譯了Dmitry A.Soshnikov的關(guān)于ECMAScript-262-3 JavaScript內(nèi)部原理的文章鸠踪,
從理論角度對JavaScript中部分特性的內(nèi)部工作機制有了一定的了解媒楼。
但是掀宋,鄧爺爺說過:“實踐才是檢驗真理的唯一標(biāo)準(zhǔn)”天梧。
所以盔性,我打算通過從內(nèi)部原理來解釋一些經(jīng)常在筆試或者面試中遇到的關(guān)于JavaScript語言層面的題目來進(jìn)一步學(xué)習(xí)和掌握J(rèn)avaScript內(nèi)部工作原理。
那么腿倚,首先就是要去找那些題目,google了一圈終于找到了來自Dmitry Baranovskiy的非常著名的5個問題,
這5個問題敷燎,NCZ給出了非常清楚的解釋暂筝。
不過,我還是想嘗試下從low-level——JavaScript內(nèi)部工作機制的角度去解釋下這些問題硬贯。
好吧焕襟,我承認(rèn)我廢話很多,那就開始吧饭豹。
問題 #1
<pre><code>if (!("a" in window)) {
var a = 1;
}
alert(a);</code></pre>
- 正確答案: undefined
解釋:
這個問題,初一看感覺答案很自然是1。因為從上到下執(zhí)行下去巡蘸,if語句中的條件應(yīng)該為 true鬼癣,因為"a"的確是沒有定義啊。
隨后翘悉,順理成章地進(jìn)入 var a = 1;茫打,最后,alert出來就應(yīng)該是1妖混。
而事實上老赤,從JavaScript內(nèi)部工作原理去看,在變量對象中講過制市,
JavaScript處理上下文分為兩個階段:
- 進(jìn)入執(zhí)行上下文
- 執(zhí)行代碼
可以理解為抬旺,第一個階段是靜態(tài)處理階段,第二個階段為動態(tài)處理階段祥楣。
而在靜態(tài)處理階段开财,就會創(chuàng)建 變量對象(variable object),并且將變量申明作為屬性進(jìn)行填充荣堰。
到了執(zhí)行階段床未,才會根據(jù)執(zhí)行情況,來對變量對象中屬性(就是申明的變量)的值進(jìn)行更新振坚。
針對這個問題薇搁,其實際過程如下:
進(jìn)入執(zhí)行上下文: 創(chuàng)建VO,并填充變量申明 a渡八,VO如下所示:
<pre><code>VO(global) = {
a: undefined
}</code></pre>
所以啃洋,這個時候,a其實已經(jīng)存在了屎鳍。執(zhí)行代碼: 進(jìn)入 if語句宏娄,發(fā)現(xiàn)條件判斷 "a" in window 為true。于是就不會進(jìn)入if代碼塊逮壁,直接執(zhí)行alert語句孵坚,因此,最終為undefined。
問題 #2
<pre><code>var a = 1,
b = function a(x) {
x && a(--x);
};
alert(a);</code></pre>
- 正確答案: 1
解釋:
這個問題卖宠,第一反應(yīng)可能會是將 function a打印出來巍杈。因為明明就看到了function a了】肝椋看似筷畦,也順其自然。
但是刺洒,事實并非如此鳖宾。還是和此前一個問題一樣。從兩個階段來分析:
進(jìn)入執(zhí)行上下文: 這個時候要注意了逆航, b = function a(){}鼎文,這里的 function a并非函數(shù)申明,因為整個這個句話屬于賦值語句(assignment statement)纸泡,所以漂问,這里的 function a會被看作是函數(shù)表達(dá)式。
函數(shù)表達(dá)式是不會對VO造成影響的女揭。所以蚤假,這個時候VO中其實只有 a和x(函數(shù)形參):
<pre><code>VO(global) = {
a: undefined,
b: undefined
}</code></pre>執(zhí)行代碼: 這個時候a的值修改為1:
<pre><code>VO(global) = {
x: undefined,
a: 1
}</code></pre>
所以,最后alert(a)的結(jié)果是1吧兔。
問題 #3
<pre><code>function a(x) {
return x * 2;
}
var a;
alert(a);</code></pre>
- 正確答案: 函數(shù)a
解釋:
這個問題磷仰,很多人可能會以為是: undefined。理由可能是境蔼,明明看到了 var a定義在了function a的后面灶平,感覺應(yīng)該會覆蓋之前a的申明。
事實又是怎樣的呢箍土? 老套路逢享,從兩個階段來分析:
進(jìn)入執(zhí)行上下文: 這里出現(xiàn)了名字一樣的情況,一個是函數(shù)申明吴藻,一個是變量申明瞒爬。那么,根據(jù)變量對象
介紹的沟堡,填充VO的順序是: 函數(shù)的形參 -> 函數(shù)申明 -> 變量申明侧但。
上述例子中,變量a在函數(shù)a后面航罗,那么禀横,變量a遇到函數(shù)a怎么辦呢?還是根據(jù) 變量對象中介紹的粥血,當(dāng)變量申明遇到VO中已經(jīng)有同名的時候柏锄,不會影響已經(jīng)存在的屬性酿箭。因此,VO如下所示:
<pre><code>VO(global) = {
a: 引用了函數(shù)申明“x”
}</code></pre>執(zhí)行代碼:啥也沒變化
所以趾娃,最終的結(jié)果是:函數(shù)a七问。
問題 #4
<pre><code>function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2, 3);</code></pre>
- 正確答案: 10
解釋:
個人感覺這個問題其實不是很復(fù)雜。這里也不需要從兩個階段去分析了茫舶。根據(jù) 變量對象中介紹的,arguments對象的properties-indexes和實際傳遞的參數(shù)是共享的
也就是說刹淌,通過arguments[2]修改的參數(shù)饶氏,也會影響到a,所以有勾,這里的值是10疹启。但是,要注意的是和實際傳遞的值蔼卡,所以喊崖,如果把上述問題改成如下形式:
<pre><code>function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2);</code></pre>
結(jié)果就會是: undefined。因為雇逞,并沒有傳遞a的值荤懂。
問題 #5
<pre><code>function a() {
alert(this);
}
a.call(null);</code></pre>
- 正確答案: 全局對象(window)
解釋:
這個問題,可能會比較困惑塘砸。因為懂call的童鞋都會覺得节仿,call的時候把null傳遞為了當(dāng)前的上下文了。里面的this應(yīng)該是null才對啊掉蔬。
事實卻是: 前面都沒錯廊宪,this會是null。但是女轿,this中介紹過箭启,null是沒有任何意義的,因此蛉迹,最終會變成全局對象傅寡。
所以,這里結(jié)果就變成了全局對象婿禽。 這里還有ECMAScript-262-3標(biāo)準(zhǔn)文檔中的一句話作為證據(jù):
“If thisArg is null or undefined, the called function is passed the global object as the this value. Otherwise, the called function is passed ToObject(thisArg) as the this value.”
總結(jié)
上面這5個問題其實也只是牽涉到了JavaScript內(nèi)部原理中的部分知識點赏僧,要想了解更多,還是建議讀完JavaScript內(nèi)部原理系列
以及去看Dmitry A.Soshnikov的文章扭倾。