因此,在对javascript中null和undefined的实现进行了激烈的争论/辩论/讨论之后,我希望有人解释实现背后的原因以及为什么它们在某些情况下有所不同。 我觉得一些特别令人不安的点:
-
null == undefined
评估结果为true
-
null + 1
等于 1 但undefined + 1
等于NaN
-
if(!null)
计算结果为 true,if(null)
计算结果为 false,但null == false
计算结果为假。
我已经阅读了规范,我知道结果是如何达到的,我正在寻找决定这是规范的范式和原因。 其中一些观点,尤其是第二点,鉴于第一点,感觉非常不一致。
简短而甜蜜的版本是,JavaScript是由Netscape团队非常迅速地设计和实现的,它有一些不一致的地方,比如你指出的那些。
Internet Exploder团队尽最大努力完全复制JS,他们做得很好,以至于不一致之处也被复制了。当Netscape将JS标准化为ECMAScript时,MS是其中的一部分,基本上是说他们不允许更改标准,因为它会破坏旧代码(现有系统惯性(。不一致之处是标准化的,仅此而已。
道格拉斯·克罗克福德(Douglas Crockford(对其中一些问题进行了一系列很好的演讲。
首先,虽然许多语言在没有两种用于类似目的的方法的情况下逃脱了,但它们在 Javascript 中确实服务于不同但有些重叠的目的。 "为什么两者都有?"以前有人问过这里,我发现这个答案很好地解释了这一点。TL;DR:Javascript具有某些语言函数,可以生成不存在的值,而不是非初始化的值:
-
delete
个值 - 对象中不存在的属性
- 缺少函数参数
至于你问题中看似矛盾的地方,其实很容易用规范来解释。 (我相信甚至可以说这个解释是优雅的,尽管可能有些人会强烈反对。
分别解决每个问题:
有关此答案
- null == 未定义的计算结果为 true
的最佳解释,请参阅此答案。 简而言之,抽象的相等比较规范说它们是(非严格(相等的。
空 + 1 等于 1
- 但未定义 + 1 等于 NaN
运算符+
用作一元 +(数字转换(运算符或加法运算符,但两者都将参数路由到 ToNumber 规范,其中显示:
参数类型 — 结果
未定义 — NaN
空 — +0
布尔值 — 如果参数为真,则结果为 1。如果参数为 false,则结果为 +0。
数字 — 结果等于输入参数(无转换(。
换句话说,null + 1
变得+0 + 1
,undefined + 1
变得NaN + 1
,这总是NaN
的。
- if(!null( 的计算结果为 true,if(null( 的计算结果为 false,但 null == false 的计算结果为 false。
如您所知,!
是 Logical Not 运算符,它对表达式执行 ToBoolean 转换。 这是一种截断。
if
语句(if (expr)
(在expr上执行隐式布尔比较。 因此,请查看上述两个if
语句中的 expr 类型:
-
if (!null)
:expr 是!null
给定逻辑非运算符 (!
( 的结果,它是一个布尔值。 -
if (null)
:exprnull
这意味着没有执行转换。
由于逻辑非运算符执行实际转换,因此在其他情况下也会发生同样的事情,并且实际上并不像您看起来那样具有逻辑矛盾:
-
if (!"" == !undefined)
= 真 - 当然,
if ("" == undefined)
= 假。
最好将它们视为用于不同目的的完全不同的对象:
null
用于"没有价值"。语言很少使用它,但主机环境经常使用它来表示"无值"。例如,document.getElementById
返回不存在的元素的null
。同样,HTMLScriptElement
的仅限 IE 属性onreadystatechange
设置为 null
,而不是 undefined
,以表示尽管该属性存在,但当前未设置。通常,最好在自己的代码中使用null
而不是undefined
,并为以下情况保留undefined
:
undefined
用于"甚至没有设置或甚至不存在"。在许多情况下,它是"默认"的,例如访问未定义的属性(如非IE浏览器中onreadystatechange
HTMLScriptElement
(,没有return
语句的方法的默认返回值,函数参数的默认值,当调用函数时使用的参数少于其声明的参数时函数参数的默认值,等等。
这样,将null
视为"有效值"很有用,只是一个表示特殊事物的值。而undefined
更像是语言层面的东西。
当然,在某些边缘情况下,这些推理并不完全成立;这些主要是出于遗留原因。但是有一个区别,这是一个有意义的区别。
至于你的痛点,多来自==
算子的邪恶或类型胁迫:
-
null == undefined
:不要使用==
运算符,因为它本质上是一堆当时看起来很直观的向后兼容性规则。 -
null + 1 === 1
vs.undefined + 1 === NaN
:+
运算符在评估之前对Number
进行强制类型。null
胁迫0
(+null === 0
(,而undefined
胁迫NaN
(isNaN(+undefined) === true
(。 -
if (!null)
,if (null)
,null == false
:if评估其论点的"真实性"或"虚假性",这与==
规则的混乱无关。null
是假的,!null
是真实的,但==
的规则不会让null == false
.
null == undefined
的计算结果确实为真,但null === undefined
的计算结果为假。
这两个语句的区别在于相等运算符。Javascript 中的双等会在比较它们之前将两个项目转换为相同的类型;对于null == undefined
,这意味着在比较完成之前,null
被转换为一个未定义的变量,因此相等。
我们可以用字符串和整数来证明相同的效果:"12" == 12
为真,但"12" === 12
为假。
这个例子为我们提供了一种更简单的方式来讨论您的下一点,即为每个点添加一个。在上面的例子中,在整数上加 1 显然会得到 13
,但使用字符串 "12" + 1
会给我们一个字符串"121"
。这是完全有道理的,你不会想要任何其他方式,但是使用双等运算符,原始的两个值被报告为相等。
这里的教训是始终优先使用三等运算符而不是双等运算符,除非您有比较不同类型的变量的特定需求。
你的最后一点表明了null
的善变本质。这是一个奇特的野兽,任何曾经尝试使用可空数据库字段的人都会告诉你。Null在计算机科学中有一个非常具体的定义,它在多种语言中以类似的方式实现,所以你描述的情况并不是一个特殊的Javascript怪异。空很奇怪。不要指望它的行为像false
的替代名称,因为它不是那样工作的。内置infinity
值的行为方式可能
不过,Javascript确实有其怪异之处。你可能有兴趣阅读 http://wtfjs.com/,它有关于Javascript所做的一大堆奇怪事情的条目。其中相当多与null
和undefined
有关(你知道实际上可以重新定义内置undefined
对象的值吗?!(,其中大多数都带有关于实际发生的事情和原因的解释。它可能有助于向您展示为什么事情会以它们的方式运作,并且肯定会有助于向您展示要避免的事情!如果不出意外,看看人们试图对糟糕的语言进行一些辱骂,这是一个很好的娱乐性阅读。