简单探索 js 中 something >> 0 的原理


本文总阅读量

关于这个问题是今天改公司项目小程序的一个bug时看到的,修复这个bug的解决方法是需要引入 String.prototype.padStart 的 polyfill,所以我就顺带扫了一眼这个 polyfill 里面的实现是怎样的,结果发现这么一行:

1
2
// truncate if number or convert non-number to 0
target = target >> 0;

我倒不是对这个代码本身的作用有什么疑问,毕竟之前看过好多关于 js 技巧的文章,其中都介绍过这种写法,并且自己也在实际工作中运用过多次。无论之前看过它几次,写过它几次,都没有追究它的原理到底是什么。至于要说当时为什么没有追究,原因一方面是因为没有时间,另一方面是当时自己水平也比较差,什么 ECMAScript 标准根本无从看起啊。经过不懈努力,终于觉得自己可以看懂一些规范了,所以借这个机会来根据规范看看它的原理是什么。

关于这行代码具体使用了什么语法以及达到的效果我就不废话了。第一步,我们需要从规范的哪里看起呢?因为完整的规范很长,我们不可能从头看起。我们需要根据规范的目录和已知代码来定位我们需要查找的具体章节。根据上面的代码可以得知,其中的操作符是一个右移操作符,如果英语比较好的或者对计算机术语比较熟悉,可以很快联想到 right-shift 这个词语(如果不好也可以根据词典得知),然后应用一下搜索大法就好了,在规范中搜索 right-shirt 相关的内容,然后再目中录就可以定位到以下信息:

1
1.12.8.4 The Signed Right Shift Operator ( >> )

这就好办了,直接跳到 12.8.4 章节,估计要看的主要内容都在这个章节中了,瞬间缩小了查询范围。

跳转到这个章节以后,看一下描述,如下:

Performs a sign-filling bitwise right shift operation on the left operand by the amount specified by the right operand.

应该是没错了,继续往下看,可以发现 12.8.4.1 中详细介绍了右移操作符的相关规范。

大概流程可以简单理解为,分别求得右移操作符左右两侧表达式的值后(其中包含一些取值、校验、转换逻辑),之后按照右移逻辑返回一个32位有符号整数。关于代码,我们比较感兴趣的是操作符左侧所对应的取值逻辑,所以这里我们需要详细看关于操作符左侧取值的逻辑,相关的步骤包含 1、2、3、7、8,依次是:

  • 第一步很简单,就是将 ShiftExpression 求值,记作 lref
  • 第二步对 lref 求值,记作 lval,求值的过程参考 GetValue
  • 第三步根据 ReturnIfAbrupt 来判定 lval 是否是异常值
  • 第七步对 lval 进行转换,记作 lnum,转换的过程参考 ToInt32
  • 第八步根据 ReturnIfAbrupt 来判定 lnum 是否是异常值

光看这个步骤是没有任何用处的,所以还需要继续看一下规范中关于 GetValue 和 ToInt32 的章节,这次就不用搜索了,直接点击链接跳转过去即可。

GetValue 如下:

ToInt32 如下:

这两个方法的详细过程就不说明了,以一个简单的例子大概理一下流程,比如使用以上代码时,假设 target 的类型是字符串,比如:

1
2
3
4
var target = 'a'

target = target >> 0
console.log(target) // 0

根据 GetValue 的逻辑可以发现,走到第二步就返回该值了,因为它是一个基础数据类型,所以 lval 的值为 ‘a’。之后带入 ToInt32(‘a’) 根据流程第一步的说明,首先进行 ToNumber(‘a’),关于 ToNumber 的规范比较长,这里就不截图了,总之最后会返回 NaN。然后顺着流程往下走,到第三步就会发现,最终 ToInt32(‘a’) 会返回 +0。

之后带入之前右移操作规范的 10 和 11 步就会得知,’a’ >> 0 等价于 +0 >> 0,最终的结果是 +0。对于其他的情况,在测试的基础上并带入以上流程,马上就会得知其原因,这里就不赘述了。

最后想说的是,我认为对于这种颗粒度的知识没有必要专门投入时间去学习和掌握,因为太过细小和零碎。但是当我们遇到一些自己不懂或者不熟悉的东西时,一定要有意识去寻根问底,这样积少成多,精通 js 早晚会变成现实。

目录