[译] 理解 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前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索