[譯] 理解 JavaScript 中的 undefined

與其餘的語言相比,JavaScript 中 undefined 的概念是有些使人困惑的。特別是試圖去理解 ReferenceError(「x is not defined」)以及如何針對它們寫出優雅的代碼是很使人沮喪的。javascript

本文是我試圖把這件事情弄清楚的一些嘗試。若是你還不熟悉 JavaScript 中變量和屬性的區別(包括內部的 VariableObject),那麼最好先去閱讀一下個人上一篇文章前端

什麼是 undefined?

在 JavaScript 中有 Undefined (type)、undefined (value) 和 undefined (variable)。java

Undefined (type) 是 JavaScript 的內置類型。android

undefined (value) 是 Undefined 類型的惟一的值。任何未被賦值的屬性都被假定爲 undefined(ECMA 4.3.9 和 4.3.10)。沒有 return 語句的函數,或者 return 空的函數將返回 undefined。函數中沒有被定義的參數的值也被認爲是 undefined。ios

var a;
typeof a; //"undefined"
 
window.b;
typeof window.b; //"undefined"
 
var c = (function() {})();
typeof c; //"undefined"
 
var d = (function(e) {return e})();
typeof d; //"undefined"
複製代碼

undefined (variable) 是一個初始值爲 undefined (value) 的全局屬性,由於它是一個全局屬性,咱們還能夠將其做爲變量訪問。爲了保持一致性,我在本文中統一稱它爲變量。git

typeof undefined; //"undefined"
 
var f = 2;
f = undefined; //re-assigning to undefined (variable)
typeof f; //"undefined"
複製代碼

從 ECMA 3 開始,它能夠被從新賦值:github

undefined = "washing machine"; //把一個字符串賦值給 undefined (變量)
typeof undefined //"string"
 
f = undefined;
typeof f; //"string"
f; //"washing machine"
複製代碼

毋庸置疑,給 undefined 變量從新賦值是很是很差的作法。事實上,ECMA 5 不容許這樣作(不過,在當前的瀏覽器中,只有 Safari 強制執行了)。web

而後是 null?

是的,通常都很好理解,可是還須要重申的是:undefinednull 不一樣,null 表示有意的缺乏值的原始值。undefinednull 惟一的類似之處是,它們都爲 false。後端

因此,什麼是 ReferenceError(引用錯誤)?

ReferenceError 說明檢測到了一個無效的引用值。(ECMA 5 15.11.6.3)瀏覽器

在實際項目中,這意味着當 JavaScript 試圖獲取一個不可被解析的引用時,會拋出 ReferenceError。(還有一些其餘的狀況會拋出 ReferenceError,尤爲是在 ECMA 5 嚴格模式下運行的時候。若是你有興趣的話,能夠看本文末尾的閱讀列表。)

須要注意不一樣瀏覽器發出的消息語法是如何變化的,正如咱們將看到的,這些信息沒有一個是特別有啓發性的:

alert(foo)
//FF/Chrome: foo is not defined
//IE: foo is undefined
//Safari: can't find variable foo 複製代碼

仍然不清楚「沒法解析的引用(unresolvable reference)」?

在 ECMA 術語中,引用由基值(base value)和引用名(reference name)構成(ECMA 5 8.7 - 我再次忽略了嚴格模式。還要注意,ECMA 3 的術語略有不一樣,但實際意義是相同的)。

若是引用是屬性,那麼基值和引用名位於 . 的兩側(或第一個括號或其餘):

window.foo; //base value = window, reference name = foo;
a.b; //base value = a, reference name = b;
myObj['create']; // base value = myObj, reference name = 'create';
//Safari, Chrome, IE8+ only
Object.defineProperty(window,"foo", {value: "hello"}); //base value = window, reference name = foo;
複製代碼

對於變量引用,基值是當前執行上下文的 VariableObject。全局上下文的 VariableObject 是全局對象自己(瀏覽器中的 window)。每一個函數上下文都有一個抽象的變量對象,稱爲 ActivationObject。

var foo; //base value = window, reference name = foo
function a() {
    var b; base value = <code>ActivationObject</code>, reference name = b
}
複製代碼

若是基值是 undefined,則認爲引用是沒法被解析的。

所以,若是在 . 以前的變量值爲 undefined,那麼屬性引用是不可被解析的。下面的示例本會拋出一個 ReferenceError,但實際上它不會,由於 TypeError 會先被拋出。這是由於屬性的基值受 CheckObjectCoercible (ECMA 5 9.10 到 11.2.1)的影響,在它嘗試將 Undefined 類型轉換爲 Object 的時候會拋出 TypeError。(感謝 kangax 在 twitter 上提早發佈的消息)

var foo;
foo.bar; //TypeError(基值,foo 是未定義的)
bar.baz; //ReferenceError(bar 是不能被解析的)
undefined.foo; //TypeError(基值是未定義的)
複製代碼

變量引用永遠會被解析,由於 var 關鍵字確保 VariableObject 老是被賦給基值。

根據定義,既不是屬性也不是變量的引用是不可解析的,而且會拋出一個 ReferenceError:

foo; //ReferenceError
複製代碼

上面的 JavaScript 中沒有看到顯式的基值,所以會查找 VariableObject 來引用名稱爲 foo 的屬性。肯定 foo 沒有基值,而後拋出 ReferenceError。

可是 foo 不是一個未聲明的變量嗎?

技術上不是的。雖然咱們有時會發現 「undeclared variable」 是一個錯誤診斷時有用的術語,但實際上,在變量被聲明以前不是變量。

那麼隱式全局變量呢?

的確,從未被 var 關鍵字聲明過的標識符將被建立爲全局變量 —— 但只有當它們被賦值時纔會這樣。

function a() {
    alert(foo); //ReferenceError
    bar = [1,2,3]; //沒有錯誤,foo 是全局的
}
a();
bar; //"1,2,3"
複製代碼

固然,這很煩人。若是 JavaScript 在遇到沒法解析的引用時始終拋出 ReferenceErrors 那就更好了(實際上這是它在 ECMA 嚴格模式下所作的)。

何時須要針對 ReferenceError 進行編碼?

若是你的代碼寫得夠好的話,其實不多須要這樣作。咱們已經看到,在典型的用法中,只有一種方法能夠得到不可解析的引用:使用既不是屬性也不是變量的僅在語法上正確的引用。在大多數狀況下,確保記住 var 關鍵字能夠避免這種狀況。只有在引用只存在於某些瀏覽器或第三方代碼中的變量時,纔會出現運行時異常。

一個很好的例子是 console。在 Webkit 瀏覽器中,console 是內置的,console 的屬性老是可用的。然而 firefox 中的 console 依賴於安裝和打開Firebug(或其餘附加組件)。IE7 沒有 console,IE8 有 console,但 console 屬性只在 IE 開發工具啓動時存在。顯然 Opera 有 console,但我歷來沒有使用過。

結論是,下面的代碼片斷在瀏覽器中運行時極可能會拋出 ReferenceError:

console.log(new Date());
複製代碼

如何對可能不存在的變量進行編碼?

檢查一個不可解析的引用並且不拋出 ReferenceError 的一種方法是使用 typeof 關鍵字。

if (typeof console != "undefined") {
    console.log(new Date());
}
複製代碼

然而,這在我看來老是很繁瑣的,更不用說可疑的了(它不是引用名稱是 undefined,而是基值爲 undefined)。可是不管如何,我更喜歡保留 typeof 來進行類型檢查。

幸運的是,還有另外一種方法:咱們已經知道,若是 undefined 屬性的基值被定義,那麼它就不會拋出 ReferenceError —— 並且因爲 console 屬於全局對象,咱們就能夠這樣作:

window.console && console.log(new Date());
複製代碼

實際上,只須要檢查全局上下文中是否存在變量(函數中存在其餘執行上下文,並且你能夠控制本身的函數中存在哪些變量)。因此,理論上你應該可以避免使用 typeof 來檢查引用錯誤。

我在哪裏能夠閱讀更多?

Mozilla 開發者中心:undefined

Angus Croll:JavaScript 中的變量與屬性

Juriy Zaytsev (「kangax」):理解 Delete

Dmitry A. Soshnikov:ECMA-262-3 詳解:第 2 章 Variable 對象

ECMA-262 第五版標準文檔

undefined:4.3.9, 4.3.10, 8.1

Reference Error:8.7.1, 8.7.2, 10.2.1, 10.2.1.1.4, 10.2.1.2.4, and 11.13.1.

ECMAScript 的嚴格模式 Annex C

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索