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

如果你的第一门编程语言不是 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 × 240.1001 × 25 = 1.001 × 24

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

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

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

3ff0 0000 0000 0000  =  1c000 0000 0000 0000  =  -23fd5 5555 5555 5555  ~  1/30000 0000 0000 0000  =  08000 0000 0000 0000  =  -07ff0 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  // 1000unsigned char flags;flags = LOG_ERRORS;                                 // 0001flags = LOG_ERRORS | LOG_WARNINGS | LOG_INCOMING;   // 1011

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

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

var a, b;a = 2e9;   // 2000000000a << 1;    // -294967296// fxck!我只想装了个逼用左移1位给 a * 2,但是结果是什么鬼!!!a = parseInt('100000000', 16); // 4294967296b = parseInt('1111', 2);       // 15a | 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:

  1. Let lref be the result of evaluating A.
  2. Let lval be GetValue(lref).
  3. Let rref be the result of evaluating B.
  4. Let rval be GetValue(rref).
  5. Let lnum be ToInt32(lval).
  6. Let rnum be ToInt32(rval).
  7. 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 中别使用位操作符。

原文发布于微信公众号 - HTML5学堂(h5course-com)

原文发表时间:2015-12-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏携程技术中心

干货 | 深入理解Python装饰器

作者简介 曾凡伟,携程信息安全部高级安全工程师,2015年加入携程,主要负责安全自动化产品的设计和研发,包括各类扫描器、漏洞管理平台、安全SaaS平台等。 Py...

2884
来自专栏阮一峰的网络日志

Javascript继承机制的设计思想

我一直很难理解Javascript语言的继承机制。 它没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分,全靠一种很奇...

2824
来自专栏积累沉淀

Java设计模式(六)----适配器模式

适配器模式 1、概述 2、适配器模式的用途 3、模式中的角色 4、实现方式 5、类适配器和对象适配器的权衡 6、缺省适配模式 1、概述  适...

1958
来自专栏cnblogs

论JavaScript的作用域

     一直以来本人认为想深入了解一门语言,不光是让自己变成撸sir,更需要时间的锤炼。能经得起时间考验的东西更值得拥有。学习和使用Javascript一晃都...

18710
来自专栏醒者呆

融会贯通——最常用的面向对象设计原则“合成复用原则”

复用一个类的时候,多使用对象的组合/聚合的关联关系,而不是继承。 之前提到的“依赖倒转原则”,是以里氏代换原则为基础的实现开闭原则目标的手段,这一条路线涉及到的...

2708
来自专栏zhisheng

面向对象设计原则之依赖倒转原则

前两天的算法排序文章,《Java常用排序算法/程序员必须掌握的8大排序算法(上)》、《Java常用排序算法/程序员必须掌握的8大排序算法(下)》,没看的可以点上...

3428
来自专栏彭湖湾的编程世界

【javascript】详解变量,值,类型和宿主对象

JS数据类型 JS类型分类 ? 讲到类型, 首先要说的当然是JS的类型分类, 对于这一点,《javascript高级语言程序设计》和《你不知道的javasvri...

1786
来自专栏木可大大

编写优雅代码的最佳实践

Robert Martin曾说过"在代码阅读中说脏话的频率是衡量代码质量额唯一标准"。同时,代码的写法应当使别人理解它所需的时间最小化,也就是说我们写的代码是给...

742
来自专栏牛客网

头条实习面经

【每日一语】真实人生中,我们往往在大势底定无可更改时才迟迟进场,却又在胜败未分的浑沌中提早离席。——翁贝托·埃科《开头与结尾》

1112
来自专栏Netkiller

Solidity 智能合约开发语言·数据类型

中国广东省深圳市龙华新区民治街道溪山美地 518131 +86 13113668890 <netkiller@msn.com>

59810

扫描关注云+社区