为什么不要在 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)<sup>sign</sup> × mantissa × 2<sup>exponent</sup>
而为了将尾数规格化,并做到尽量提高精确度,就需要把尾数精确在 [1,2) 的区间内,这样便可省去前导的1。比如:
11.101 × 2<sup>3</sup> = 1.1101 × 2<sup>4</sup> 0.1001 × 2<sup>5</sup> = 1.001 × 2<sup>4</sup>
并且标准规定指数部分使用 0x3ff 作为偏移量,也就有了双精度浮点数的一般公式:
(-1)<sup>sign</sup> × 1.mantissa × 2<sup>exponent - 0x3ff</sup>
举一些例子,应该能帮助你理解这个公式:
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”,使用双精度浮点数来表示的最大安全整数为 -2<sup>53</sup>+1 到 2<sup>53</sup>-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 中别使用位操作符。
为什么不要在 JavaScript 中使用位操作符?的更多相关文章
- javascript 中的new操作符的理解
new 操作符 在有上面的基础概念的介绍之后,在加上new操作符,我们就能完成传统面向对象的class + new的方式创建对象,在Javascript中,我们将这类方式成为Pseudoclassic ...
- JavaScript 中的相等操作符 ( 详解 [] == []、[] == ![]、{} == !{} )
ECMAScript 中的相等操作符由两个等于号 ( == ) 表示,如果两个操作数相等,则返回 true. 相等操作符会先转换操作数(通常称为强制转型),然后比较它们的相等性. 在转换不同的数据类型 ...
- 浅谈Javascript中的void操作符
由于JS表达式偏啰嗦,于是最近便开始采用Coffeescript来减轻负担.举个栗子,当我想取屋子里的第一条dog时,首先要判断house对象是否存在,然后再判断house.dogs是否存在,最后取h ...
- JavaScript中的十种操作符
① 一元操作符(参与的只有一个变量) 前置递增递减(语句解析到递增/递减时值就被改变了) 后置递增递减(整个语句执行后值再改变) 递增递减也可用于字符串,布尔值,对象等,结果都将是数值: ;v ...
- 通过例子深入理解javascript中的new操作符
1.首先看一道题目 function Cat(name,age){ this.name=name; this.age=age; } console.log(new Cat('miaomiao',18) ...
- Javascript中的位运算符和技巧
ECMAScript 整数有两种类型,即有符号整数(允许用正数和负数)和无符号整数(只允许用正数).在 ECMAScript 中,所有整数字面量默认都是有符号整数,这意味着什么呢? 有符号整数使用 3 ...
- JavaScript中的typeof操作符用法实例
在Web前端开发中,我们经常需要判断变量的数据类型.鉴于ECMAScript是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof就是负责提供这方便信息的操作符. 对一个值使用t ...
- javascript中的位运算,
罗浮宫群里又有讨论位运算符号|了,做过一段时间php,数据库保存布尔值数据经常用到,比如100110 就表明了六个属性的是与否,极大减少了数据量..] ECMAScript 中位运算跟其他语言一样的. ...
- javascript中的一元操作符
题目如下: var s1 = "01"; var s2 = "1.1"; var s3 = "z"; var b = false; var ...
随机推荐
- [原]Android Fragment 入门介绍
Fragment Fragment 产生,优点,用途,使用方法简介 1 Fragmeng简介 自从Android 3.0中引入fragments 的概念,根据词海的翻译可以译为:碎片.片段.其上的是为 ...
- C根据排序字符串
#include<stdio.h> #include<string.h> #include <stdlib.h> #define STR_LEN_MAX 100 c ...
- composer "Failed to decode zlib stream"
dockerFile 中安装composer.... RUN curl -s -f -L -o /tmp/installer.php https://raw.githubusercontent.com ...
- Caffe 碎碎念
Window Data Layer window data layer 的数据是存在硬盘上的图片, 需要在一个txt里指定用于训练或测试的图片以及bounding box, bounding box ...
- if 语句 写了return 报错
- TCP/UDP区别&&心跳包机制【转】
转自:https://www.jianshu.com/p/6d93a3c21c34 UDP:用户数据报协议:主要用在实时性要求比较高的以及对质量相对较弱的地方.但是面对现在高质量的线路不会容易丢包,除 ...
- H5页面关于android软键盘弹出顶起底部元素的解决方案
应用场景:用div在移动端页面设置一个底部工具栏,css的代码大概如下: .tool{ width: 100%; height: 60px; position: fixed; left: 0px; b ...
- (windows下)安装mysql
一.先从mysql的官网上下载对应版本的mysql zip包(适用于windows下的) 二.解压zip包放到自定义的文件夹下(我放的是e盘,路径为E:\mysql-5.6.40-winx64) 三. ...
- intellij 出现“Usage of API documented as @since 1.8+”的解决办法
intellij 出现“Usage of API documented as @since 1.8+”的解决办法 Usage of API documented as @since 1.8+ This ...
- TcxGrid 标题头高度