4

from http://segmentfault.com/q/1010000000381586#a-1020000000381836

随意的略谈,文中有错的概率接近1。所以并不要对本文过于当真,任何不满请随意留言开喷。

注意本文严格区分Null/null,Undefined/undefined

Null & Undefined

从各种语言的语义上看,Undefined相当于一个变量并没有明确的被赋值。很多语言将其视为一种错误或异常:

# Py2.7
print(a) # NameError: name 'a' is not defined

而Null相当于变量被明确指定了没有值,而不是由于意外的原因被忽略掉了:

# Py2.7
b = None
print(b) # None

Null和Undefined体现的区别在于:

  • 是否被赋值了是定性问题,具体赋的什么值是定量问题。
  • 遗忘了赋值一般是人为错误,赋了空值是肯定是正当逻辑
  • Undefined一般是无心的,Null肯定是故意的。

也就是说从函数传参的语义上讲:

  • 如果是空值,传Null是最好的。
  • 不明确传值,不代表参数指定为空,而是尊重参数的默认值。
  • 如果不明确传值,参数也没有默认值,多数语言会认为“缺少必要参数”是一个错误。

JavaScript的怪异之处

nullundefined是JavaScript的两颗雷。

JS的怪异之处就在于undefined真的是一个可以使用的值。在其他语言需要用特别方法取消一个变量的定义(甚至定义之后不能取消)的时候:

# Py2.7
a = 1
del a
a # NameError: name 'a' is not defined

JS只需把undefined赋给变量:

a = undefined;

这个行为和臭名昭著的VB6,在不开启“强制变量声明”(Option Explicit)时的行为是非常相似的:

MsgBox(Str(undef1)) ' 我记得是Nothing,转换后为vbNullString
' -----------------
Option Explicit
MsgBox(Str(undef1)) ' 我记得是编译错误:变量未定义

在函数传参中undefined的用途在于弥补JavaScript没有参数默认值语法的缺欠:

function f(x) {
    if (x === undefined) {x = 1;}
    return x;
}
console.log(f()); // 1

从这个意义上,还好JavaScript没有设计function f(x=1)的语法。要不然给x传undefined,就不知道语义是尊重x的默认值,还是把x的值覆写成undefined了……

nullundefined参与运算的麻烦

JS的null如果进入运算,真的会被解析成为0false

(1 + null) # 1
(1 * null) # 0
(1 * null) # Infinity

undefined进入运算,一律得到NaN

(1 + undefined) # NaN
(1 * undefined) # NaN
(1 / undefined) # NaN

麻烦就在于这些现象都是不报错的。对于其他语言,想都想得到:

# Py2.7
1+None # TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'
1+a    # NameError: name 'a' is not defined

这也就是说在JS程序中,如果忘记判断外部输入的变量是否存在,会使程序产生不确定的行为。虽然外部输入不检验肯定是程序员的错……但从语言设计上看,这比其他语言干脆报错,在可靠性上不可否认的差了很多。

nullundefined的判断

nullundefined逻辑判断时都认为是false。

但是如果碰到大坑==的时候……自己看吧。我是不理解==的时候这TM都是个什么语义:

var foo;
console.log(foo == null); // true
console.log(foo == undefined); // true
console.log(foo === null); // false
console.log(foo === undefined); // true
console.log(null == undefined); // true

JS不明确,编程者要明确

避免问题只有一种逻辑:编程者明确自己的语义。

  • 不要放过小错误,在最小的程序上也要注意语义的明确性。玩JS没有这个习惯确实容易挂的。
  • 如果明确为空,那就是null,而不是忽略掉。尽量避免容忍参数的默认值,因为程序员不知道(也关心不到)未来上游代码会如何改变参数为默认值时的行为。
  • 使用外部指定的变量前,检验存在性,必要时抛出错误。永远不假设变量有值。
  • 判断一个量已定义且非空,只使用:if (a !== null && a !== undefined)

JS的坑要多少有多少,需要编程者的认真控制,避免程序出现不可预测的行为。从这一个角度上看,JavaScript让编程者很累,绝对是非常非常烂(faulty)的。不知道有多少人还记得《JavaScript: The Definitive Guide》和《JavaScript: The Good Parts》的那个笑话(图)?如果没有对应的利益,恐怕避免使用JavaScript总是个不差的想法。

不过Unix哲学也指导我们:更坏就是更好。虽然有问题但实用可行,快速面世并不断发展的方案,比某些大公司闷头完善,结果最后黄花菜都凉了才扔出来的东西要强。

其他参考

http://justjavac.com/javascript/2013/04/14/javascript-quirk-2-two-non-values-undefined-and-null.html
相当值得一读(尤其值得注意一下域名)

http://www.ruanyifeng.com/blog/2011/06/10_design_defects_in_javascript.html
这篇文章的意义是了解一下=====运算符,在碰到nullundefined时候的怪异行为。
如果你决定去读这篇文章,请注意我不认为原作者的这句话是个正确的判断:

在编程实践中,null几乎没用,根本不应该设计它。


沙渺
21.8k 声望1.1k 粉丝

1998年入行,普通的电脑老玩家。Web、嵌入式Linux和电子产品设计研究者。