为什么 `null >= 0 && null <= 0` 而不是 `null == 0`?

175

我需要编写一个程序来实现一个变量加1的操作,如果该变量是number类型则将其加1,否则将其赋值为0。该变量最初的值是nullundefined

第一次实现的代码是v >= 0 ? v += 1 : v = 0,因为我认为任何不是数字的东西都会使算术表达式为假,但是这是错误的,因为null >= 0被计算为true。然后我了解到null的行为类似于0,以下表达式都被计算为true。

  • null >= 0 && null <= 0
  • !(null < 0 || null > 0)
  • null + 1 === 1
  • 1 / null === Infinity
  • Math.pow(42, null) === 1

当然,null不是0。null == 0计算结果为false。这使得看似重言的表达式(v >= 0 && v <= 0) === (v == 0)为false。

为什么null虽然不是0,但却像0一样呢?


3
他在谈论Javascript。你的示例是用PHP写的。在PHP中,operator==以一种特殊的方式比较值。您可以进行一些非常疯狂的比较,比如"10" == "1e1"(这是真的)。如果您使用operator===,则会得到完全不同的结果,因为它检查类型是否匹配以及值。请查看此链接:http://www.php.net/manual/en/language.operators.comparison.php - Pijusn
PHP中的'=='运算符确实以一种“特殊”的方式工作。 - Two-Bit Alchemist
如果您的要求是从1开始计数而不是0,那么有一种非常简洁的方法可以增加初始为nullundefined的计数器:c = -~c // 如果为null/undefined,则结果为1;如果已经是数字,则递增 - Ates Goral
1
undefined 是一个变量值,用于未初始化的变量。另一方面,null 是一个空对象值,不应与数字混合使用。null 不应与数字组合使用,因此 null 不必像数字一样运作。 - Matthew
1
@AtesGoral - 简洁,但不明显。值得提醒人们,每当做一些不明显的事情时,请添加注释以解释代码的作用。在大多数情况下,我认为这是“过早优化”,因为它以微不足道的性能提升为代价换来了清晰度。 - ToolmakerSteve
6个回答

246

您真正关心的问题似乎是:

为什么:

null >= 0; // true

但是:

null == 0; // false

大于或等于运算符(>=)实际上会进行类型转换(ToPrimitive),并使用Number类型的提示信息,所有关系运算符都有这种行为。

null等于运算符(==)中被特殊处理。简单来说,它只会被强制转换为undefined

null == null; // true
null == undefined; // true

false'''0'[]这样的值会被强制转换为数字类型,它们都将强制转换为零。

您可以在抽象相等比较算法抽象关系比较算法中查看此过程的详细信息。

总之:

  • 关系比较:如果两个值都不是字符串类型,则对两个值调用ToNumber。这与在前面添加+的操作相同,对于null,则强制转换为0

  • 相等比较:仅对字符串、数字和布尔值调用ToNumber


2
嗨CMS,根据您的解释,null原始值为0,因此0>=0返回true,==却返回false。但是根据ECMA算法,如果Type(x)是Object且Type(y)是String或Number,则返回比较ToPrimitive(x)== y的结果。那么在这种情况下应该返回true,请解释一下。 - bharath muppa
对我来说,这个答案并没有提供一个“答案” - null在等于运算符(==)中被特殊处理。简而言之,它只是强制转换为undefined: - 然后呢?你能解释一下为什么null >= 0吗? :) - Andrey Deineko
1
CMS的答案的其余部分在这里:抽象关系比较算法其中第3点解释了如果两个值都不是字符串类型,那么将对它们都调用ToNumber。这与在前面添加+相同,对于null强制转换为0。相等性只对字符串、数字和布尔值调用ToNumber。 - Michael Liquori
9
好的描述,但我不喜欢它。在任何语言中 (x == 0 || x > 0) 应该等同于 (x >= 0)。JavaScript 是一种愚蠢的语言。 - John Henckel
4
实际上,这只是规范中的一个错误(因为从数学上讲是错误的),由于数百万网站依赖于 null 比较,所以无法做任何更改。^^' - mahieddine
难道不应该是,如果两个值都不是数字,则对它们调用ToNumber函数,而你写的是如果两个值都不是字符串,则对它们调用ToNumber函数。 - Suraj Jain

23

我想把问题扩展一下,以进一步提高问题的可见性:

null >= 0; //true
null <= 0; //true
null == 0; //false
null > 0;  //false
null < 0;  //false

这根本没有意义。就像人类语言一样,这些东西需要被死记硬背。


4
如上所述,只有一个例外可以解释为如何处理null的==,否则在所有情况下,使用Number(null)将null转换为0。 - Sourabh Ranka

10

JavaScript有严格和类型转换比较。

null >= 0;为true,但是(null==0)||(null>0)为false。

null <= 0;为true,但是(null==0)||(null<0)为false。

"" >= 0也为true。

对于关系抽象比较(<=,>=),在比较之前,操作数首先被转换为基本类型,然后转换为相同的类型。

typeof null返回"object"。

当类型为对象时,javascript尝试将对象(即null)字符串化,执行以下步骤 (ECMAScript 2015):

  1. 如果未传递PreferredType,则将hint设置为"default"。
  2. 否则,如果PreferredTypehint String,则将hint设置为"string"。
  3. 否则,如果PreferredTypehint Number,则将hint设置为"number"。
  4. exoticToPrim成为GetMethod(input, @@toPrimitive)
  5. ReturnIfAbrupt(exoticToPrim)
  6. 如果exoticToPrim未定义,则执行以下步骤:
    a) 让结果为Call(exoticToPrim, input, «hint»)
    b) ReturnIfAbrupt(result)
    c) 如果Type(result)不是Object,则返回结果。
    d) 抛出TypeError异常。
  7. 如果hint是"default",则将hint设置为"number"。
  8. 返回OrdinaryToPrimitive(input,hint)

提示的允许值为"default"、"number"和"string"。Date对象在内置ECMAScript对象中是唯一的,它将"default"视为等同于"string"。 所有其他内置ECMAScript对象都将"default"视为等同于"number"。(ECMAScript 20.3.4.45

因此,我认为null转换为0。


10
console.log( null > 0 );  // (1) false
console.log( null == 0 ); // (2) false
console.log( null >= 0 ); // (3) true

从数学上讲,这很奇怪。最后一个结果表明,“null大于或等于零”,因此在上面的比较中必须为真,但它们都是假的。

原因是等于检查==和比较> < >= <=的工作方式不同。比较将null转换为数字,并将其视为0。这就是为什么(3) null >= 0true而(1) null > 0false的原因。

另一方面,对于未定义undefinednull的等式检查==被定义为,不进行任何转换,它们相等,不等于其他任何值。这就是为什么(2) null == 0false的原因。


2
我也遇到了同样的问题!!目前我的唯一解决方案是分开处理。
var a = null;
var b = undefined;

if (a===0||a>0){ } //return false  !work!
if (b===0||b>0){ } //return false  !work!

//but 
if (a>=0){ } //return true !

1
可能更清晰的做法是:if (a!=null && a>=0)。这样可以澄清不仅仅是使用 >=,因为 "a 可能为 null(或 undefined,也等于 '== null')"。 - ToolmakerSteve

0

看起来检查 x >= 0 的方式是 !(x < 0) 这样回应是有道理的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接