2017-05-07 61 views
1

據我所知,Javascript不編譯,它只運行。所以應該沒有編譯時錯誤,只有運行時錯誤。那麼爲什麼這個代碼不工作?運行一個未定義參考的函數

function show() { console.log(x); } 
(function() { 
    var x = 42; 
    show(); 
})() 

我的問題不在於如何使此代碼更好;我意識到這是不好的代碼,我已經知道如何解決它(見下文)。

我的問題是,爲什麼我會得到一個未捕獲ReferenceError?如果Javascript只在運行時拋出錯誤,它應該知道x == 42在它調用show()的時候,它在匿名函數中,是否正確?


工作代碼:

(function() { 
    var x = 42; 
    function show() { console.log(x); } 
    show(); 
})() 

工作的代碼,最好的選擇:在JS

function show(y) { console.log(y); } 
(function() { 
    var x = 42; 
    show(x); 
})() 
+0

'show()'範圍內不存在'x',它在匿名函數的範圍內聲明。是的,匿名函數有自己的範圍。對於您的相關問題 - Javascript是一種解釋型語言,您不需要編譯它,但是在現代JS引擎中,隨着代碼的解釋,它們會將其編譯爲機器代碼。 – skyline3000

回答

1

注意:下面的說明是ES5,不ES6的,因爲在ES6範圍的規則已經改變(由於引進放開)。

Javascript does compile。就像C++/c#等其他語言一樣,沒有像exe/IL代碼那樣的中間事物需要點擊才能開始執行。在JS執行後,編譯階段開始。

因此,當編譯發生時,編譯器會查找函數聲明併爲變量尋找var declaration。因此,對於此IIFE,

(function() { 
    var x = 42; 
    show(); 
})(); 

它確實找到一個var聲明,變量x被註冊在IIFE的範圍中。這稱爲變量提升,x在功能級別可用,在這種情況下爲IIFE。

在執行時後來,IIFE看起來是這樣的(概念):

(function() { 
    //registration of x in this function's scope has already happened at 
    //compile time. Notice absence of `var` 
    x = 42; 
    show(); 
})(); 

現在,在這個時候發動機會談的範圍,並要求x的左值參考。由於x已在IIFE中註冊,所以發動機得到一個,然後將42分配給它。

現在對於這部分:

function show() { console.log(x); } 

當節目被調用時,引擎首先會詢問放映功能爲x的範圍,因爲它不具有名爲X註冊的任何變化,全球範圍內被要求(右值引用),因爲它在編譯階段從未用全局範圍註冊過,所以在任何範圍內都找不到它,並且我們得到參考錯誤。

it should know that x == 42 at the time it calls show(), which is inside the anonymous function, correct? 

由於範圍規則,外部範圍中的變量在內部範圍內可見,但反之亦然。在IIFE內部可以看到x,而在外部範圍內則不在外部。

+0

自從ES6發佈以來,我知道'let'的作用域是塊,而不是函數,但是var變量的作用域規則是如何改變的?我認爲'var'的行爲從ES5到ES6保持不變。 – chharvey

+0

@chharvey我的意思是說,由於let關鍵字(如果使用let聲明),函數級別範圍規則不再適用於變量。 –

+0

@chharvey順便說一句,你看到的錯誤是運行時錯誤。代碼執行並拋出錯誤。 –

1

變吊裝樂趣。

這是說給你的參考錯誤,因爲你已經定義了一個閉包(在另一個函數中定義的函數)的x,這意味着它在全局範圍內不可用並且方法不知道它存在。如果你首先在全球範圍內定義它,它當然會起作用。這就是說,在ES6 +中,由於使用了letconst,範圍已經得到了顯着提高,所以除非你堅持使用vanilla JS,否則你可能會發現那些提供更加一致和可預測的編碼體驗。

這是關於這個問題的一個有用的閱讀:How do JavaScript closures work?

1

show函數獲取它聲明的範圍,而不是調用。