【译】理解 JavaScript 中的 undefined
栏目: JavaScript · 发布时间: 5年前
内容简介:与其他的语言相比,JavaScript 中 undefined 的概念是有些令人困惑的。特别是试图去理解 ReferenceError(“x is not defined”)以及如何针对它们写出优雅的代码是很令人沮丧的。本文是我试图把这件事情弄清楚的一些尝试。如果你还不熟悉 JavaScript 中变量和属性的区别(包括内部的 VariableObject),那么最好先去阅读一下我的上一篇文章。在 JavaScript 中有 Undefined (type)、undefined (value) 和 und
与其他的语言相比,JavaScript 中 undefined 的概念是有些令人困惑的。特别是试图去理解 ReferenceError(“x is not defined”)以及如何针对它们写出优雅的代码是很令人沮丧的。
本文是我试图把这件事情弄清楚的一些尝试。如果你还不熟悉 JavaScript 中变量和属性的区别(包括内部的 VariableObject),那么最好先去阅读一下我的上一篇文章。
什么是 undefined?
在 JavaScript 中有 Undefined (type)、undefined (value) 和 undefined (variable)。
Undefined (type)是 JavaScript 的内置类型。
undefined (value)是 Undefined 类型的唯一的值。任何未被赋值的属性都被假定为 undefined
(ECMA 4.3.9 和 4.3.10)。没有 return 语句的函数,或者 return 空的函数将返回 undefined。函数中没有被定义的参数的值也被认为是 undefined。
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) 的全局属性,因为它是一个全局属性,我们还可以将其作为变量访问。为了保持一致性,我在本文中统一称它为变量。
typeof undefined; //"undefined" var f = 2; f = undefined; //re-assigning to undefined (variable) typeof f; //"undefined" 复制代码
从 ECMA 3 开始,它可以被重新赋值:
undefined = "washing machine"; //把一个字符串赋值给 undefined (变量) typeof undefined //"string" f = undefined; typeof f; //"string" f; //"washing machine" 复制代码
毋庸置疑,给 undefined 变量重新赋值是非常不好的做法。事实上,ECMA 5 不允许这样做(不过,在当前的浏览器中,只有 Safari 强制执行了)。
然后是 null?
是的,一般都很好理解,但是还需要重申的是: undefined
与 null
不同, null
表示 有意的
缺少值的原始值。 undefined
和 null
唯一的相似之处是,它们都为 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
来检查引用错误。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 理解原型其实是理解原型链
- 要理解深度学习,必须突破常规视角去理解优化
- 深入理解java虚拟机(1) -- 理解HotSpot内存区域
- 荐 【C++100问】深入理解理解顶层const和底层const
- 深入理解 HTTPS
- 深入理解 HTTPS
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First EJB(中文版)
KathySierra,Ber / 中国电力出版社 / 2006-9 / 79.00元
有些人只是想通过认证来取悦挑剔的老板,但相信你不是这种人。确实,你也想通过Su n认证业务组件开发人员(SCBCD)考试,但不仅如此,你还需要真正把EJB用到实处。你要构建应用,要对付最后期限,如果通过考试之后第二天早上就把你学过的EJB知识忘得一干二净,你肯定会受不了。 我们会看着你稳稳当当地通过考试,而且会帮你在实际中使用EJB。你会深入地了解EJB体系结构、会话、实体和消息驱动......一起来看看 《Head First EJB(中文版)》 这本书的介绍吧!