今天在看【你不知道的JavaScript中卷】第一部分第四章的4.3节显示强制类型转换,里面有一个小部分“2.奇特的~运算符”(P59)有提到取反运算~,但是在日常开发中我从来没用到取反运算~。
下文中会有很多内容是直接从书中摘抄下来的,我就不搞二次加工了。
文章提到~42的值为-43,但是不知道怎么转换的,已经把很基础的东西给忘记了。
这里先提一下正负数在二进制的表示。
正负数原码、反码、补码
下面以8位有符号整型来示例。
- 原码:符号位 + 数值位
- 反码:
- 正数反码:与原码一致
- 负数反码:符号位 + 数值位取反
- 补码
- 正数补码:与原码一致
- 负数补码:负数反码 + 1
| 数字 | 符号位 | 数值位 | 原码 | 反码 | 补码 |
|---|---|---|---|---|---|
| 5 | 0 | 0000101 | 00000101 | 00000101 | 00000101 |
| -5 | 1 | 0000101 | 10000101 | 11111010 | 11111011 |
如果不使用补码,只用原码相加的结果是错误的, 但是使用补码后,对应正负数的补码相加结果为0,符合逻辑。
取反运算符~及其作用
-1是一个“哨位值”,哨位值是那些在各个类型中补赋予了特殊含义的值。在C语言中我们用-1来代表函数执行失败,用大于等于0的值来代表函数执行成功。
JavaScript中字符串的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作为失败时的返回值,这些细节应该被屏蔽掉。
这里~取返运算就有作用了,他可以很好处理这个-1,因为~-1值为0,是一个falsy value,可以在代码中屏蔽掉底层实现细节。
var a = -1
~a // 0 <-- falsy value
在代码中我们可以如下写法:
var a = "Hello World"
~a.indexOf("lo") // -4 <-- truthy value
if (~a.indexOf("lo")) { // true
// 找到匹配
}
~a.indexOf("ol") // 0 <-- falsy value
!~a.indexOf("ol") // true <-- !0
if (!~a.indexOf("ol")) { // true
// 没有找到匹配
}
这里如果indexOf()的返回值为-1,那么~运算符会将其值转换为假值0,其他情况一律转换为真值。
你可能想问为什么~-1结果是0,前面一小节特意回忆了一下原码、反码、补码,下面是-1的各种码:
| 数字 | 符号位 | 数值位 | 原码 | 反码 | 补码 |
|---|---|---|---|---|---|
| -1 | 1 | 0000001 | 10000001 | 11111110 | 11111111 |
-1的补码是11111111,此时我们做~取反运算得到00000000,最后得到的值就为0。
个人感想
上面这种处理-1的方法可能真的很符合那种隐藏底层的实现细节,但是对于我来说应该还是会去使用>= 0这种方式,毕竟~取反运算符基乎很少用,也不容易理解,我在想如果遇上了!~a.indexOf("ol")这样的代码,估计是不容易阅读,首先会想到是位运算符,然后要想为什么要这样处理,当然这只是我平常开发时的想法,我想对自己说:很局限,没深度。
主要是这个思维,我从来没有想到过!继续加油吧!