Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript中奇特的~运算符 #22

Open
Aaaaaaaty opened this issue Sep 29, 2017 · 3 comments
Open

JavaScript中奇特的~运算符 #22

Aaaaaaaty opened this issue Sep 29, 2017 · 3 comments

Comments

@Aaaaaaaty
Copy link
Owner

Aaaaaaaty commented Sep 29, 2017

写在最前

本次分享一下并不是很常用的按位非运算符~的原理以及一点点用法。

欢迎关注我的博客,不定期更新中——

JavaScript小众系列开始更新啦

——何时完结不确定,写多少看我会多少!这是已经更新的地址:

这个系列旨在对一些人们不常用遇到的知识点,以及可能常用到但不曾深入了解的部分做一个重新梳理,虽然可能有些部分看起来没有什么用,因为平时开发真的用不到!但个人认为糟粕也好精华也罢里面全部蕴藏着JS一些偏本质的东西或者说底层规范,如果能适当避开舒适区来看这些小细节,也许对自己也会有些帮助~文章更新在我的博客,欢迎不定期关注。

~的规则是什么

看下规范里面的定义的~:

产生式 UnaryExpression : ~ UnaryExpression 按照下面的过程执行:

  1. 令 expr 为解释执行 UnaryExpression 的结果。

  2. 令 oldValue 为 ToInt32(GetValue(expr))。

  3. 返回 oldValue 按位取反的结果。结果为 32位 有符号整数。

总结一下即将数字进行抽象Toint32操作,再进行按位取反。那么再来看下关于Toint32:
image

数字进行Toint32操作会转化成32位有符号数,第一位为符号位,后面31位为表示整数数值。最后对数字进行按位取反即可得到~转换后的结果。

举个🌰

//以18为例子,进行Toint32抽象操作
//将18表示为二进制形式
     0    000 0000 0000 0000 0000 0000 0001 0010
//|符号位||              数值部分                |  

//按位取反
    1     111 1111 1111 1111 1111 1111 1110 1101
//|符号位||              数值部分                |  

可以发现现在将18进行了按位非操作之后这个数变成了一个负数,同时我们可以看到这么多个1。。感觉这个负数很大啊?所以~18会是一个很大的负数么?我们打印看下:

image

好像和预料中的有些出入?

负数是如何存成二进制的?

我们可以直接打印看下:

image

然而这并不是我们想要的,会有这个结果是因为ECMAScript采用了这样简单的方式来避免开发者接触一些底层的操作,真实的存储二进制负数的方式应该是采用补码的形式。而也正是由于补码的操作我们才能解释为什么~18 === -19

补码

生成补码的三个步骤:

确定该数字的非负版本的二进制表示(例如,要计算 -18的二进制补码,首先要确定 18 的二进制表示)

求得二进制反码,即要把 0 替换为 1,把 1 替换为 0

在二进制反码上加 1

我们先不管为什么负数要用补码来存储,先来看下~18 === -19是如何而来的。
根据上述计算-19的补码步骤:

//将19表示为二进制形式
     0    000 0000 0000 0000 0000 0000 0001 0011
//|符号位||              数值部分                |  

//按位取反
    1     111 1111 1111 1111 1111 1111 1110 1100
//|符号位||              数值部分                |  

//反码加一
    1     111 1111 1111 1111 1111 1111 1110 1100
                                               1
    --------------------------------------------
    1     111 1111 1111 1111 1111 1111 1110 1101
//同时 18的按位取反表示为:
    1     111 1111 1111 1111 1111 1111 1110 1101

所以我们可以看到,由于补码为按位取反并+1,~ 为按位取反,那么也就可以说明为什么~18 === -19 同时我们也可以得出结论即:

~x === -(x+1)

那么为什么负数存储为补码?

因为计算机在做二进制运算的时候,不希望考虑运算数的符号,全部希望执行加法操作来得出正确结果,由此引入了补码的概念。比如我们试图用4-2的结果与4+2的补码结果比对来进行说明:

4 - 2 =>
0100 - 0010 = 0010
4 + (-2) =>
0010 + 1110 = 0010(相加超过位数,溢出自动丢失)

~的应用

对哨位值进行~操作

哨位值一般可以表示失败的意思。例如js中的哨位值如-1,当你执行indexOf操作时,如果找不到目标则返回-1,同时~-1 = 0,由此我们可以将代码转变为:

if(str.indexOf('js') != -1) => if(~indexOf('js'))

那么为什么不使用>=0或者!= -1这种操作呢,在《你不知道的JavaScript》一书中,将之成为“抽象渗漏”,意思是在代码中暴露了底层实现细节,我们可以选择屏蔽掉细节。故 ~ 可以和indexOf进行配合判断真假值,核心思路就是运用了~x === -(x+1)

浮点数取整

我们现在知道~ 会进行按位取反的过程中会进行Toint32抽象操作,在这个过程中会将浮点数去掉,只对前面32位整数进行处理。故我们可以使用~进行以下操作:

~~3.12 = 3

同时需要注意由于~的特性,小数点后面的部分是直接被干掉的,而不是会进行Math.floor之类的四舍五入操作。

参考资料

最后

惯例po作者的博客,不定时更新中——
有问题欢迎在issues下交流。

@deepkolos
Copy link

deepkolos commented Dec 28, 2017

之前喵mui的代码见过 来方便判断indexOf的结果

default

看到标题就回复了, 结果看下来, 发现文中也提到了, 哈哈

@Aaaaaaaty
Copy link
Owner Author

@deepkolos 对因为indexOf的边界值是-1 正好和~配合的很好

@cnwhy
Copy link

cnwhy commented Jan 3, 2018

绝大多数情况下 if(~n) 是没有问题,但是javascript的位操作符都是基于32位的,所以确定 n 值要小于32位整数,不然会有意外的惊喜。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants