为什么不要在 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:

  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 中别使用位操作符。

时间: 2024-10-31 04:58:07

为什么不要在 JavaScript 中使用位操作符?的相关文章

PHP和Javascript中的逻辑操作符&amp;&amp;和||的比较

几乎所有的语言都有&&(且)和||(或)的逻辑操作符,&&用于判断多个表达式的时候,如果为真(true),则继续判断下一个表达式,如果为假(false),则停止表达式解析,即只有当所有表达式都为真(true),才返回真(true),如果某一个表达式为假(false),则结果就(false):而||(或)恰好相反,也是对多个表达式依次判断,如果表达式为真(true),则终止表达式的逻辑判断,其结果为真(true).这两个逻辑操作符都存在短路这个行为: &&判断

通过例子深入理解javascript中的new操作符

1.首先看一道题目 1 function Cat(name,age){ 2 this.name=name; 3 this.age=age; 4 } 5 console.log(new Cat('miaomiao',18)); 6 //Cat {name: "miaomiao", age: 18} 2.那么这里面的this指的是什么呢 1 function Cat(name,age){ 2 console.log(this);//Cat {} 3 this.name=name; 4 th

JavaScript中的typeof操作符用法实例

在Web前端开发中,我们经常需要判断变量的数据类型.鉴于ECMAScript是松散类型的,因此需要有一种手段来检测给定变量的数据类型——typeof就是负责提供这方便信息的操作符. 对一个值使用typeof操作符可能返回下列某个字符串: “undefined”——如果这个值未定义“boolean”——如果这个值是布尔值“string”——如果这个值是字符串“number”——如果这个值是数值“object”——如果这个是对象或null“function”——如果这个值是函数 常用的typeof操

javascript中的一元操作符

题目如下: var s1 = "01"; var s2 = "1.1"; var s3 = "z"; var b = false; var f = 1.1; var = { valueOf: function() { return -1; } }; s1= -s1; //-1 s2 = -s2; //-1.1 s3 = -s3; //NaN b = -b; //0 f = -f; //-1.1 o = -o; //1 解释: 在对非数值应用一元加

javascript中神奇的(+)加操作符

javascript是一门神奇的语言,这没神奇的语言中有一个神奇的加操作符. 常用的加操作符我们可以用来做: 加法运算,例如:alert(1+2); ==>3 字符串连接,例如:alert(“a”+”b”);==>”ab” 高级一点的还有“+=”,也是做以上两种操作的. 昨天在javascript丛林群里问了问题:怎么把“2000-09-11 19:22”这个日期格式字符串转换成毫秒数? 斩梦人天天马上回答我: +new Date(‘2000-09-11 19:22′),试了一下不行,正确的应

JavaScript中逻辑操作符“==”与“===”的区别

Summary JavaScript中,逻辑操作符“===”会先检查操作数的数据类型,对不同的数据类型会返回false. 而“==”对不同类型的操作数进行比较时,会进行类型转换后再比较. Description 在JavaScript中进行判断的两种逻辑操作符: == (相等) === (严格相等/全等) 由于JavaScript是弱类型的脚本语言(weakly typed),用于作比较的两个操作数的类型允许不一致.而这两个逻辑操作符最大的区别在于对操作数类型的容忍度. 即:如果两个操作数的数据

javaScript中__proto__与prototype的区别与联系

[转]javaScript中__proto__与prototype的区别与联系 2014-5-4阅读490 评论0 最近在学习javascript的原型,发现了__proto__与prototype,学问很大,于是研究了一下. 首先解释一下什么是原型? 原型是一个对象,其他对象可以通过它实现属性继承. 对象又是什么呢? 在javascript中,一个对象就是任何无序键值对的集合,如果它不是一个主数据类型(undefined,null,boolean,number,array,string),那它

JavaScript中的原型和继承

请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans 原型车.这些车虽然是由"奥迪"或"标致"这些厂商制造的,可它们并不是你在街上或速公路上所见到的那类汽车.它们是专为参加高速耐力赛事而制造出来的. 厂家投入巨额资金,用于研发.设计.制造这些原型车,而工程师们总是努力尝试将这项工程做到极致.他们在合金.生物燃料.制动技术

【转】JavaScript中的原型和继承

请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans 原型车.这些车虽然是由“奥迪”或“标致”这些厂商制造的,可它们并不是你在街上或速公路上所见到的那类汽车.它们是专为参加高速耐力赛事而制造出来的. 厂家投入巨额资金,用于研发.设计.制造这些原型车,而工程师们总是努力尝试将这项工程做到极致.他们在合金.生物燃料.制动技术.轮胎的化合物成分和安全特性上