JavaScript 中的布尔类型

与许多其他编程语言类似,在 JavaScript 中,布尔类型是基础类型之一(其余分别是字符串、数字、对象、未定义、空)它有两个值,true 和 false,分别表示逻辑上的 真 与 假。

Object 类型
Object 类型也是基础类型之一,提供描述 需要使用复合成员的方式来一同描述的类型 的方式。很容易理解,它就是 JavaScript 中所有类型的起始类型(基类型)。虽然日常编程实践中我们不将布尔、字符串或数字等基础类型的变量当做“对象”来看待,但它们确实都是“对象”的实例。
举证来说,你可以调用任意非空值的 toString 方法,但实际上它们的类型本身并没有定义 toString,因为这个方法是从 Object 类型那里继承而来。

布尔类型也继承自 Object 类型。
我们平常所见的 true 或 false,都是 Boolean 类型的特殊实例。我们知道布尔类型只有两个值,即 true 和 false;但作为一个可以随意实例化(面向对象编程中的概念,创建一个类的新实例)的类型,布尔类型并不只有两个实例。即 true 或 false 与 new Boolean() 是不相同的。
您可以跟随我做下面的试验:

1
2
3
4
var t = true; 
var nt = new Boolean(true); 
console.log( t == nt );        // true 
console.log( t === nt );        // false

第3行的结果为 true,因为 t 与 nt 相等:这是意料中的,因为布尔只有两个值,我使用 true 值实例化 Boolean 得到 nt 的值与字面量 true 相等;
第4行的结果为 false,因为该表达式检查 t 与 nt 是否相同,结果显示它们不同。

还有一点,提醒您注意。
在上述 t 和 nt 上分别使用 typeof 操作符得到的结果是不一样的:

1
typeof t   // 得到 "boolean"  

typeof nt // 得到 “object”

区别在于,t 是字面量布尔值,而 nt 是经显式(明确的、故意的)实例化的布尔对象。
我想,实际编程实践中大概不会有人真会像 new Boolean 这样去用布尔类型,在这里提出来,是想介绍一下基础类型的值与它的“对象”化(这一过程称为*装箱*)的区别。

 

JavaScript 里到处是布尔类型

从上一节的介绍中,您了解到布尔类型除了我们通常所见的 true 与 false 之外,还有另一种表现形式,即 Boolean 类型的实例。
在这里,我不得不拓展这个范围,因为在 JavaScript 中,不光 Boolean 类型的实例是布尔类型的,所有 JavaScript 中的表达式都可以被当作布尔值来处理,所有。而且,在执行引擎处理这些原本不是布尔值的表达式时,它们与 Boolean 的实例的“级别”是一样的,处理方式是完全一致的。

举例来说,我们知道 while 语句需要接受一个布尔值作为继续循环的判断条件,因此您可以这样:

1
2
3
while ( true ){
  // do something cool
} 

有了上面的规律,您还可以这样:

1
2
3
while ( window ){
   // do something cool
} 

在浏览器中,上面两个 while 块起到的作用是一致的,因为 window 对象始终被当作 true。
不光这样,您甚至还可以在一般的任意对象上使用只有原本只有布尔值才能使用的操作符,比如 &&、|| 和 !
上面的示例可以改成这样:

1
2
3
while ( window || false ){
    // do something cool
} 

括号中的 ( window || false ) 由于 window 被当作 true,那整个表达式相当于 ( true || false ),最终运算得到的结果相当于 true,也就是说这段代码与上面两个 while 仍将产生相同的运行结果。
之所以会产生这样的结果,是因为 JavaScript 作为一种动态类型的编程语言,执行引擎在需要时,将会自动按照运行期间的需要来按一定的规则来自动转换被处理的表达式的值的类型。我们还可以在字符串拼接等场合见到这种自动的类型转换机制。在上面的例子中,while 语句需要接受布尔值作为判断条件,那执行引擎就会试图将括号中的表达式自动转换为布尔值。如果将 while 换成 if 语句也可以看到同样的结果。
但,只是自动转换而已,这最多说明 window 与 true 之间存在某种规则使 window 在需要时可以被转换为 true 值,并不表明 window 与 true 有任何相等关系。
我想您早已知道下述两个表达式必将都会输出 false:

1
2
console.log( window == true );  // false
console.log( window === true );  // false

 

比较关系与类型转换

那为什么 window 被当作 true 处理呢?也就是说,在上一节中我所述的由其他类型转换为布尔值的“一定的规则”具体是什么呢?
具体来讲,除了下面规定的在转换为 布尔值 时会被转换为 false,其他都被转换为 true:
false(布尔 false)
null(空值)
undefined(未定义)
NaN(非数字)
0(数字零)
“(空字符串)

显然,window 不在上述值之列,因此自动类型转换处理后被当作 true 值对待。
给任意表达式中运用布尔非运算符将会直接得到它对应的布尔值的 非 值,通过两次 非 操作(!!)来获得表达式对应的布尔值。

1
2
3
!!( window )  // true
!!( 0 )    // false
!!( '' )    // false

读到这里,请您试着解释下面的代码:

1
2
3
var c = new Boolean( false );
console.log( c == false ) // true
console.log( !c )    // false

 

布尔值的 或、且 运算

布尔值有三种运算,分别为
第一种运算 的作用是取反,即 非true 为 false,反之亦然。
这里提一下第二种与第三种运算。
是一种二元运算,也就是由两个布尔值参与的运算,它们的结果是“参与运算的两者其中之一的值为true,则最终的结果是true;其余情形时,最终的结果是false”;
类似于 或,也是一种二元运算,结果的计算方法是“参与运算的两者其中之一的值为false,则最终的结果是false;其余情形时,最终的结果是true”。
逻辑运算的有趣在于它的 非true即false 的特性。
琢磨一下,我们发现,还可以这样叙述:
的结果是“两者均为false时,结果是false;其余,true”;
的结果是“两者均为true,结果是true;其余,false”。
更进一步,我们又可以发现:
运算中,如果第一个值为 true 时,我们不需要看第二个值,就可以知道最终结果是 true;
运算中有对应的类似规律:如果第一个值为 false 时,我们不需要看第二个值,就可以知道最终结果是 false。
相必聪明的你已经猜到,实际上,在处理布尔运算时,正是采用了这种“短路”的解析和执行方式来处理布尔运算的。

此外,当有上述“自动类型转换机制”参与进来时,情况稍微复杂了一点点。因为所有 JavaScript 表达式都可以被当作布尔值来处理,当需要时。这个简单,我们只要给它们运用布尔运算符就可以制造这种“需要”了。
实际的情况是:
当给任意两个表达式运用逻辑 或、且 运算符时,将按下面的规则来处理:
运算中,如果第一个表达式对应的布尔值为 true 时,最终结果是 第一个表达式,否则结果是 第二个表达式;
运算中,如果第一个表达式对应的布尔值为 false 时,最终结果是 第一个表达式,否则结果是 第二个表达式;
注意上面两句的措辞,给任意两个表达式运用逻辑 或、且 运算符时,得到的结果并不一定是布尔值,而是参与运算的两个表达式的原始值之一。
看下面的几个示例:

1
2
3
window && window.console        // window.console
false || window        // window
1 || 0    // 1 

在事件的多浏览器兼容性处理中,我们经常见到这样的代码:

1
2
3
4
document.body.onclick = function(e){
    e = e || window.event;
    // .. 其他逻辑
} 

由于在非 IE 浏览器中,事件Event对象会在事件回调被调用时作为参数被传回,但IE却将事件Event对象置于window.event中,所以这样的处理可以在 e 没有值时试图获取 window.event,从而具有兼容能力。

最后,我们发现上面关于 “或 运算”和“且 运算”的叙述里面有“如果……则,否则……”这样的词,是不是跟 if..else..的语法含义一致了?其实上面这事件处理的例子本来也可以这样写:

1
2
3
4
5
if(e){
   e = e;
}else{
   e = window.event;
} 

为什么不这样写呢?因为这样写太啰嗦、累赘了。
其实,我们平时写的代码对于电脑来说,同样都“太啰嗦、累赘了”,呵呵,电脑没抱怨而已。从啰嗦形式到简洁形式的过程称为精简(minify),这个动作叫压缩(compress)。
要不,试着考虑一下用这种布尔运算精简一下您之前的代码?