一个常被人忽视的地方是
~
运算符(即字位操作“非”)相关的强制类型转换,它很让人
费解,以至于了解它的开发人员也常常对其敬而远之。秉承本书的一贯宗旨,我们在此深
入探讨一下
~
有哪些用处。
在
2.3.5
节中,我们讲过字位运算符只适用于
32
位整数,运算符会强制操作数使用
32
位
格式。这是通过抽象操作
ToInt32
来实现的(
ES5
规范
9.5
节 )。
ToInt32
首先执行
ToNumber
强制类型转换,比如
"123"
会先被转换为
123
,然后再执行
ToInt32
。
虽然严格说来并非强制类型转换(因为返回值类型并没有发生变化),但字位运算符(如
|
和
~
)和某些特殊数字一起使用时会产生类似强制类型转换的效果,返回另外一个数字。
60
|
第
4
章
例如
|
运算符(字位操作“或”)的空操作(
no-op
)
0 | x
,它仅执行
ToInt32
转换(第
2
章中介绍过):
0 | -0; // 0
0 | NaN; // 0
0 | Infinity; // 0
0 | -Infinity; // 0
以上这些特殊数字无法以
32
位格式呈现(因为它们来自
64
位
IEEE 754
标准,参见第
2
章),因此
ToInt32
返回
0
。
关于
0 | ___
是显式还是隐式仍存在争议。从规范的角度来说它无疑是显式的,但如果对
字位运算符没有这样深入的理解,它可能就是隐式的。为了前后保持一致,我们这里将其
视为显式。
再回到
~
。它首先将值强制类型转换为
32
位数字,然后执行字位操作“非”(对每一个字
位进行反转)。
这与
!
很相像,不仅将值强制类型转换为布尔值
<
,还对其做字位反转(参
见
4.3.3
节 )。
字位反转是个很晦涩的主题,
JavaScript
开发人员一般很少需要关心到字位级别。
对
~
还可以有另外一种诠释,源自早期的计算机科学和离散数学:
~
返回
2
的补码。这样
一来问题就清楚多了!
~x
大致等同于
-(x+1)
。很奇怪,但相对更容易说明问题:
~42; // -(42+1) ==> -43
也许你还是没有完全弄明白
~
到底是什么玩意?为什么把它放在强制类型转换一章中介
绍?稍安勿躁。
在
-(x+1)
中唯一能够得到
0
(或者严格说是
-0
)的
x
值是
-1
。也就是说如果
x
为
-1
时,
~
和一些数字值在一起会返回假值
0
,其他情况则返回真值。
然而这与我们讨论的内容有什么关系呢?
-1
是一个“哨位值”,哨位值是那些在各个类型中(这里是数字)被赋予了特殊含义的值。
在
C
语言中我们用
-1
来代表函数执行失败,用大于等于
0
的值来代表函数执行成功。
强制类型转换
|
61
JavaScript
中字符串的
indexOf(..)
方法也遵循这一惯例,该方法在字符串中搜索指定的子
字符串,如果找到就返回子字符串所在的位置(从
0
开始),否则返回
-1
。
indexOf(..)
不仅能够得到子字符串的位置,还可以用来检查字符串中是否包含指定的子
字符串,相当于一个条件判断。例如:
var a = "Hello World";
if (a.indexOf( "lo" ) >= 0) { // true
//
找到匹配!
}
if (a.indexOf( "lo" ) != -1) { // true
//
找到匹配!
}
if (a.indexOf( "ol" ) < 0) { // true
//
没有找到匹配!
}
if (a.indexOf( "ol" ) == -1) { // true
//
没有找到匹配!
}
>= 0
和
== -1
这样的写法不是很好,称为“抽象渗漏”,意思是在代码中暴露了底层的实
现细节,这里是指用
-1
作为失败时的返回值,这些细节应该被屏蔽掉。
现在我们终于明白
~
有什么用处了!
~
和
indexOf()
一起可以将结果强制类型转换(实际
上仅仅是转换)为真
/
假值:
var a = "Hello World";
~a.indexOf( "lo" ); // -4 <--
真值
!
if (~a.indexOf( "lo" )) { // true
//
找到匹配!
}
~a.indexOf( "ol" ); // 0 <--
假值
!
!~a.indexOf( "ol" ); // true
if (!~a.indexOf( "ol" )) { // true
//
没有找到匹配!
}
如果
indexOf(..)
返回
-1
,
~
将其转换为假值
0
,其他情况一律转换为真值。
由
-(x+1)
推断
~-1
的结果应该是
-0
,然而实际上结果是
0
,因为它是字位操
作而非数学运算。
62
|
第
4
章
从技术角度来说,
if (~a.indexOf(..))
仍然是对
indexOf(..)
的返回结果进行
隐式
强制类
型转换,
0
转换为
false
,其他情况转换为
true
。但我觉得
~
更像
显式
强制类型转换,前
提是我对它有充分的理解。
个人认为
~
比
>= 0
和
== -1
更简洁