昨天發的《大叔手記(19):你真懂JavaScript嗎?》裏面的5個題目,有不少回答,發現強人仍是不少的,不少人都所有答對了。javascript
今天咱們來對這5個題目詳細分析一下,但願對你們有所幫助。html
注:前端
問題來自大名鼎鼎的前端架構師Baranovskiy的帖子《So, you think you know JavaScript?》。 答案也是來自大名鼎鼎的JS牛人Nicholas C. Zakas的帖子《Answering Baranovskiy’s JavaScript quiz》——《JavaScript高級程序設計》一書的原做者 (但題目2的解釋貌似有點問題)
OK,咱們先看第一題java
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了。post
第三,你須要理解該題目的意思是,變量聲明被提早了,但變量賦值沒有,由於這行代碼包括了變量聲明和變量賦值。
你能夠將語句拆分爲以下代碼:
var a; //聲明 a = 1; //初始化賦值
當變量聲明和賦值在一塊兒用的時候,JavaScript引擎會自動將它分爲兩部以便將變量聲明提早,不將賦值的步驟提早是由於他有可能影響代碼執行出不可預期的結果。
因此,知道了這些概念之後,從新回頭看一下題目的代碼,其實就等價於:
var a; if (!("a" in window)) { a = 1; } alert(a);
這樣,題目的意思就很是清楚了:首先聲明a,而後判斷a是否在存在,若是不存在就賦值爲1,很明顯a永遠在window裏存在,這個賦值語句永遠不會執行,因此結果是undefined。
大叔注:提早這個詞語顯得有點迷惑了,其實就是執行上下文的關係,由於執行上下文分2個階段:進入執行上下文和執行代碼,在進入執行上下文的時候,建立變量對象VO裏已經有了:函數的全部形參、全部的函數聲明、全部的變量聲明
VO(global) = { a: undefined }
這個時候a已經有了;
而後執行代碼的時候纔開始走if語句,詳細信息請查看《深刻理解JavaScript系列(12):變量對象(Variable Object)》中的處理上下文代碼的2個階段小節。
大叔注:相信不少人都是認爲a在裏面不可訪問,結果纔是undefined的吧,實際上是已經有了,只不過初始值是undefined,而不是不可訪問。
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了,詳細內容請參考《深刻理解JavaScript系列(2):揭祕命名函數表達式》中的內容。
大叔注:安裝ECMAScript規範,做者對函數聲明覆蓋變量聲明的解釋其實不許確的,正確的理解應該是以下:
進入執行上下文: 這裏出現了名字同樣的狀況,一個是函數申明,一個是變量申明。那麼,根據深刻理解JavaScript系列(12):變量對象(Variable Object)介紹的,填充VO的順序是: 函數的形參 -> 函數申明 -> 變量申明。
上述例子中,變量a在函數a後面,那麼,變量a遇到函數a怎麼辦呢?仍是根據變量對象中介紹的,當變量申明遇到VO中已經有同名的時候,不會影響已經存在的屬性。而函數表達式不會影響VO的內容,因此b只有在執行的時候纔會觸發裏面的內容。
function a(x) { return x * 2; } var a; alert(a);
這個題目就是題目2裏的大叔加的註釋了,也就是函數聲明和變量聲明的關係和影響,遇到同名的函數聲明,VO不會從新定義,因此這時候全局的VO應該是以下這樣的:
VO(global) = { a: 引用了函數聲明「a」 }
而執行a的時候,相應地就彈出了函數a的內容了。
function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2, 3);
關於這個題目,NC搬出了262-3的規範出來解釋,其實從《深刻理解JavaScript系列(12):變量對象(Variable Object)》中的函數上下文中的變量對象一節就能夠清楚地知道,活動對象是在進入函數上下文時刻被建立的,它經過函數的arguments屬性初始化。arguments屬性的值是Arguments對象:
AO = { arguments: <ArgO> };
Arguments對象是活動對象的一個屬性,它包括以下屬性:
這個共享其實不是真正的共享一個內存地址,而是2個不一樣的內存地址,使用JavaScript引擎來保證2個值是隨時同樣的,固然這也有一個前提,那就是這個索引值要小於你傳入的參數個數,也就是說若是你只傳入2個參數,而還繼續使用arguments[2]賦值的話,就會不一致,例如:
function b(x, y, a) {
arguments[2] = 10;
alert(a);
}
b(1, 2);
這時候由於沒傳遞第三個參數a,因此賦值10之後,alert(a)的結果依然是undefined,而不是10,但以下代碼彈出的結果依然是10,由於和a沒有關係。
function b(x, y, a) {
arguments[2] = 10;
alert(arguments[2]);
}
b(1, 2);
function a() { alert(this); } a.call(null);
這個題目能夠說是最簡單的,也是最詭異的,由於若是沒學到它的定義的話,打死也不會知道結果的,關於這個題目,咱們先來了解2個概念。
首先,就是this值是如何定義的,當一個方法在對象上調用的時候,this就指向到了該對象上,例如:
var object = { method: function() { alert(this === object); //true } } object.method();
上面的代碼,調用method()的時候this被指向到調用它的object對象上,但在全局做用域裏,this是等價於window(瀏覽器中,非瀏覽器裏等價於global),在若是一個function的定義不是屬於一個對象屬性的時候(也就是單獨定義的函數),函數內部的this也是等價於window的,例如:
function method() { alert(this === window); //true } method();
瞭解了上述概念以後,咱們再來了解一下call()是作什麼的,call方法做爲一個function執行表明該方法可讓另一個對象做爲調用者來調用,call方法的第一個參數是對象調用者,隨後的其它參數是要傳給調用method的參數(若是聲明瞭的話),例如:
function method() { alert(this === window); } method(); //true method.call(document); //false
第一個依然是true沒什麼好說的,第二個傳入的調用對象是document,天然不會等於window,因此彈出了false。
另外,根據ECMAScript262規範規定:若是第一個參數傳入的對象調用者是null或者undefined的話,call方法將把全局對象(也就是window)做爲this的值。因此,無論你何時傳入null,其this都是全局對象window,因此該題目能夠理解成以下代碼:
function a() { alert(this); } a.call(window);
因此彈出的結果是[object Window]就很容易理解了。
這5個題目雖然貌似有點偏,但實際上考察的依然是基本概念,只有熟知了這些基本概念才能寫出高質量代碼。
關於JavaScript的基本核心內容和理解基本上在該系列就到此爲止了,接下來的章節除了把五大原則剩餘的2篇補全依然,會再加兩篇關於DOM的文章,而後就開始轉向整理關於JavaScript模式與設計模式相關的文章了(大概10篇左右),隨後再會花幾個章節來一個實戰系列。
若是你們有興趣,能夠繼續研究下面的一些題目,詳細經過這些題目也能夠再次加深對JavaScript基礎核心特性的理解。
大叔注:這些題目也是來自出這5個題目的人,固然若是你能答對4個及以上而且想拿高工資的話,請聯繫我。
本文已同步至目錄索引:深刻理解JavaScript系列
深刻理解JavaScript系列文章,包括了原創,翻譯,轉載等各種型的文章,若是對你有用,請推薦支持一把,給大叔寫做的動力。