资讯专栏INFORMATION COLUMN

为什么不要在 JavaScript 中使用位操作符?

eternalshallow / 384人阅读

摘要:但事实上,位操作符并不是这么认为的。再者,在中使用位操作符的地方毕竟太少,如果你执意使用位操作符,未来维护这段代码的人又对中的位操作符的坑不熟悉,这也会造成不利的影响。所以,我对大家的建议是,尽量在中别使用位操作符。

  

本文最早在我的个人博客《咀嚼之味》发布:http://jerryzou.com

如果你的第一门编程语言不是 JavaScript,而是 C++ 或 Java,那么一开始你大概会看不惯 JavaScript 的数字类型。在 JavaScript 中的数字类型是不区分什么 Int,Float,Double,Decimal 的。咳咳,我说的当然是在 ES6 之前的 JS,在 ES6 的新标准中提出了像 Int8Array 这样新的数据类型。不过这不是本文叙述的重点,暂且就不谈啦。本文将更着重地谈 JS 的数字类型以及作用于它的位操作符,而关于包装对象 Number 的更多了解可以看拔赤翻译的《JavaScript设计模式》

数字类型的本质

实际上,JavaScript的数字类型的本质就是一个基于 IEEE 754 标准的双精度 64 位的浮点数。按照标准,它的数据结构如图示这样:由1位符号位,11位指数部分以及52位尾数部分构成。

在浮点数中,数字通常被表示为:

(-1)sign × mantissa × 2exponent

而为了将尾数规格化,并做到尽量提高精确度,就需要把尾数精确在 [1,2) 的区间内,这样便可省去前导的1。比如:

11.101 × 23 = 1.1101 × 24
0.1001 × 25 = 1.001 × 24

并且标准规定指数部分使用 0x3ff 作为偏移量,也就有了双精度浮点数的一般公式:

(-1)sign × 1.mantissa × 2exponent - 0x3ff

举一些例子,应该能帮助你理解这个公式:

3ff0 0000 0000 0000  =  1
c000 0000 0000 0000  =  -2
3fd5 5555 5555 5555  ~  1/3
0000 0000 0000 0000  =  0
8000 0000 0000 0000  =  -0
7ff0 0000 0000 0000  =  无穷大 ( 1/0 )
fff0 0000 0000 0000  =  负无穷大 ( 1/-0 )
7fef ffff ffff ffff  ~  1.7976931348623157 x 10^308 (= Number.MAX_VALUE)
433f ffff ffff ffff  =  2^53 - 1 (= Number.MAX_SAFE_INTEGER)
c33f ffff ffff ffff  =  -2^53 + 1 (= Number.MIN_SAFE_INTEGER)

得益于尾数省略的一位“1”,使用双精度浮点数来表示的最大安全整数为 -253+1 到 253-1 之间,所以如果你仅仅使用 JavaScript 中的数字类型进行一些整数运算,那么你也可以近似地将这一数字类型理解为 53 位整型。

让人又爱又恨的位操作符

熟悉 C 或者 C++ 的同学一定对位操作符不陌生。位操作符最主要的应用大概就是作为标志位与掩码。这是一种节省存储空间的高明手段,在曾经内存的大小以 KB 为单位计算时,每多一个变量就是一份额外的开销。而使用位操作符的掩码则在很大程度上缓解了这个问题:

#define LOG_ERRORS            1  // 0001
#define LOG_WARNINGS          2  // 0010
#define LOG_NOTICES           4  // 0100
#define LOG_INCOMING          8  // 1000

unsigned char flags;

flags = LOG_ERRORS;                                 // 0001
flags = LOG_ERRORS | LOG_WARNINGS | LOG_INCOMING;   // 1011

因为标志位一般只需要 1 bit,就可以保存,并没有必要为每个标志位都定义一个变量。所以按上面这种方式只使用一个变量,却可以保存大量的信息——无符号的 char 可以保存 8 个标志位,而无符号的 int 则可以同时表示 32 个标志位。

可惜位操作符在 JavaScript 中的表现就比较诡异了,因为 JavaScript 没有真正意义上的整型。看看如下代码的运行结果吧:

var a, b;

a = 2e9;   // 2000000000
a << 1;    // -294967296

// fxck!我只想装了个逼用左移1位给 a * 2,但是结果是什么鬼!!!

a = parseInt("100000000", 16); // 4294967296
b = parseInt("1111", 2);       // 15
a | b;                         // 15

// 啊啊啊,为毛我的 a 丝毫不起作用,JavaScript真是门吊诡的语言!!!

好吧,虽然我说过大家可以近似地认为,JS 的数字类型可以表示 53 位的整型。但事实上,位操作符并不是这么认为的。在 ECMAScript® Language Specification 中是这样描述位操作符的:

  

The production A : A @ B, where @ is one of the bitwise operators in the productions above, is evaluated as follows:

Let lref be the result of evaluating A.

Let lval be GetValue(lref).

Let rref be the result of evaluating B.

Let rval be GetValue(rref).

Let lnum be ToInt32(lval).

Let rnum be ToInt32(rval).

Return the result of applying the bitwise operator @ to lnum and rnum. The result is a signed 32 bit integer.

需要注意的是第5和第6步,按照ES标准,两个需要运算的值会被先转为有符号的32位整型。所以超过32位的整数会被截断,而小数部分则会被直接舍弃。

而反过来考虑,我们在什么情况下需要用到位操作符?使用左移来代替 2 的幂的乘法?Naive啊,等遇到像第一个例子的问题,你就要抓狂了。而且对一个浮点数进行左移操作是否比直接乘 2 来得效率高,这也是个值得商榷的问题。

那用来表示标志位呢?首先,现在的内存大小已经不值得我们用精简几个变量来减少存储空间了;其次呢,使用标志位也会使得代码的可读性大大下降。再者,在 JavaScript 中使用位操作符的地方毕竟太少,如果你执意使用位操作符,未来维护这段代码的人又对 JS 中的位操作符的坑不熟悉,这也会造成不利的影响。

所以,我对大家的建议是,尽量在 JavaScript 中别使用位操作符。

参考资料

维基百科:双精度浮点数

MDN:JavaScript数据结构

MDN:按位操作符

How to use bitmask?

ECMAScript® Language Specification - 11.10 Binary Bitwise Operators

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85708.html

相关文章

  • JavaScript的“黑话”

    摘要:数值表示法科学计数法是一种数学术语,将一个数表示为乘以的次方,如光速万公里每秒,在计算中通常将米做单位,则记为,而在中我们可使用科学计数法表示。以下情况会自动将数值转为科学计数法表示小数点前的数字多于位。 showImg(https://segmentfault.com/img/bVbhNqT?w=1024&h=683); 因为球是圆的,所以不论发生什么都有可能,对这点我是深信不疑的,...

    fjcgreat 评论0 收藏0
  • JavaScript 性能优化

    摘要:如果你忽略这两个步骤,那么在第二步所产生的任何修改都会触发一次重排。 更多文章 加载与执行 将标签放在前面,不要放在中,防止造成堵塞 尽量减少请求,单个100KB的文件比4个25KB的文件更快,也就是说减少页面中外链的文件会改善性能 尽量使用压缩过的JS文件,体积更小,加载更快 数据存取 使用局部变量和字面量比使用数组和对象有更少的读写消耗 尽可能使用局部变量代替全局变量 如无必...

    ad6623 评论0 收藏0
  • 高程3总结#第24章最佳实践

    摘要:也就是说避免属性查找或其他的操作。简化循环体循环体是执行最多的,所以要确保其被最大限度地优化。代码组织组织代码要考虑到可维护性并不一定是传送给浏览器的最好方式。 最佳实践 可维护性 什么是可维护性的代码 如果说代码是可维护的,它需要遵循以下特点 可理解性——其他人可以接手代码并理解它的意图和一般途径,而无需原开发人员的完整解释。 直观性——代码中的东西一看就能明白,不管其操作过程多...

    zhiwei 评论0 收藏0
  • JavaScript作符的特殊作用

    摘要:主要有以下几种位操作符一般来说,我们在中很少能用到这些位操作符,但在某些特殊情况下,这些简单的操作符却能抵得上好几行代码如果不在乎可读性的话。 Javascript主要有以下几种位操作符: AND ( & ) OR ( | ) XOR ( ^ ) NOT ( ~ ) LEFT SHIFT ( ) ZERO-FILL RIGHT SHIFT ( >>> ) 一般来说,我们在Java...

    NeverSayNever 评论0 收藏0
  • JavaScript是怎样编码数字的[How numbers are encoded in Java

    摘要:译者注规范化就是把小数点放在第一个非零数字的后面总结当指数的范围是十进制分数不是所有的十进制分数都能够非常精确的表示例如和都不能够被精确的表示成二进制浮点数。相同的,也不能被精确表示成一个十进制分数,它大概能被表示成。 在JavaScript中所有的数字都是浮点数,本篇文章将介绍这些浮点数在JavaScript内部是怎样被转为64位二进制的。我们会特别考虑整数的处理,所以读完本篇之后,...

    oysun 评论0 收藏0

发表评论

0条评论

eternalshallow

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<