JavaScript中的>>>运算符是什么,如何使用?

174

我在查看 Mozilla 的代码,它向数组添加了一个 filter 方法,其中有一行代码让我感到困惑。

var len = this.length >>> 0;

我之前从未见过JavaScript中使用 >>> 。
它是什么?它有什么作用?


@CMS True,这段代码/问题来自之前的那些;然而,这里的回答更加具体和有价值。 - Justin Johnson
2
要么是一个错误,要么Mozilla的人假设this.length可能是-1。 >>>是无符号移位运算符,因此var len将始终为0或更大。 - user347594
2
Ash Searle发现了它的用途 - 推翻JS之父(Doug Crockford)对Array.prototype.push / Array.prototype.pop的实现 - http://hexmen.com/blog/2006/12/push-and-pop/(尽管他进行了测试,哈哈)。 - Dan Beam
7个回答

239
它不仅将非数字转换为数字,而且将它们转换为可以表示为32位无符号整数的数字。虽然JavaScript的数字是双精度浮点数,但按位运算符(<<,>>,&,|和~)以32位整数的操作定义。执行按位运算将数字转换为32位有符号整数,在执行计算之前丢失任何小数和比32高的位,然后再转换回数字。因此,进行没有实际效果的按位运算(如0位向右移位>>0)是一种快速四舍五入数字并确保其处于32位int范围内的方法。此外,三倍符号>>>在执行其无符号操作后,将其计算结果转换为Number作为无符号整数,而不是其他符号整数所做的签名整数,因此它可用于将负数转换为32位二进制补码版本。使用>>>0确保您已经获得了介于0和0xFFFFFFFF之间的整数。在这种情况下,这很有用,因为ECMAScript根据32位无符号整数来定义数组索引。因此,如果要以完全复制ECMAScript第五版标准所说的方式实现array.filter,则应将数字转换为32位无符号整数。实际上,这很少有实际需求,因为希望人们不会将数组长度设置为0.5、-1、1e21或“LEMONS”。
1>>>0            === 1
-1>>>0           === 0xFFFFFFFF          -1>>0    === -1
1.7>>>0          === 1
0x100000002>>>0  === 2
1e21>>>0         === 0xDEA00000          1e21>>0  === -0x21600000
Infinity>>>0     === 0
NaN>>>0          === 0
null>>>0         === 0
'1'>>>0          === 1
'x'>>>0          === 0
Object>>>0       === 0

(*:其实,它们被定义为像浮点数一样运作。如果出于性能原因某些JavaScript引擎确实使用整数,那也不会让我感到惊讶。但这是一些你无法从中获得任何优势的具体实现细节。)


2
+2 的深度描述和表格,-1 是因为 array.length 自我验证,不能随意设置为不是整数或 0 的任何值(Firefox 抛出此错误: RangeError: invalid array length)。 - Justin Johnson
4
然而,规范故意允许许多数组函数在非数组上调用(例如通过 Array.prototype.filter.call),因此 array 可能实际上并不是一个真正的 Array:它可能是某些其他用户定义的类。(不幸的是,它不能可靠地成为 NodeList,当你真正想要这样做时,因为那是宿主对象。这只留下了唯一一个你真正会这样做的地方 - arguments 伪数组。) - bobince
很好的解释和很好的例子!不幸的是,这又是 Javascript 的另一个疯狂方面。我就是不明白为什么在收到错误类型时抛出错误会如此可怕。允许动态类型而不允许每个意外错误都创建类型转换是可能的。:( - Mike Williamson
使用 >>>0 可确保你得到一个介于 0 和 0xFFFFFFFF 之间的整数。当尝试识别评估左侧不是整数时,if 语句会是什么样子呢?'lemons'>>>0 === 0 && 0 >>>0 === 0 这个表达式会被计算为 true 吗?即使 lemons 明显是一个单词..? - Zze

59
无符号右移运算符在Mozilla的所有数组额外方法实现中使用,以确保length属性是一个无符号32位整数
数组对象的length属性在规范中被描述为:
每个数组对象都有一个长度属性,其值始终是小于232的非负整数。
这个运算符是实现它的最短方式,在内部,数组方法使用ToUint32操作,但该方法不可访问,并且存在于规范中以供实现目的。
Mozilla的数组额外实现试图遵守ECMAScript 5标准,请查看Array.prototype.indexOf方法的描述(§15.4.4.14):
正如你所看到的,他们只是想要复制ToUint32方法的行为以符合 ES5 规范在 ES3 实现上的要求,而且就像我之前说的那样,无符号右移运算符是最简单的方法。

虽然链接的数组附加功能实现可能是正确的(或接近正确),但代码仍然是一个糟糕的代码示例。也许甚至可以通过添加注释来澄清意图以解决这种情况。 - fmark
2
数组的长度是否可能不是整数?我无法想象,所以这种“ToUint32”似乎有点多余。 - Marcel Korpel
7
请注意,大多数Array.prototype方法是有意地通用的,它们可以用于类似数组的对象,例如Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;arguments对象也是一个很好的例子。对于数组对象,无法更改length属性的类型,因为它们实现了一个特殊的[[Put]]内部方法,当对length属性进行赋值时,会再次将其转换为ToUint32并采取其他操作,例如删除新长度以上的索引... - Christian C. Salvadó

34

这是无符号右移位运算符。与有符号右移位运算符的区别在于,无符号右移位运算符(>>>)从左侧填充零,而有符号右移位运算符(>>)使用符号位填充,因此在移位时保留数值的符号。


伊万,这将把它移动0个位置;那个语句不会改变任何东西。 - Dean J
3
@Ivan,通常来说,将一个值移动零个位置是毫无意义的。但这是Javascript,因此可能有一定含义。我不是Javascript专家,但这可能是确保该值在类型不明确的Javascript语言中为整数的一种方式。 - driis
2
@Ivan,请看下面Justin的回答。这实际上是一种确保len变量包含数字的方法。 - driis
1
此外,>>> 转换为整数,而一元运算符 + 不会这样做。 - recursive
this.length >>> 0 将有符号整数转换为无符号整数。就个人而言,在加载带有无符号整数的二进制文件时,我发现这很有用。 - Matt Parkins

29

Driis 已经解释了位运算符是什么以及它的作用。下面是它的含义/为什么使用它:

将任何方向移动 0 次会返回原始数字,并将 null 强制转换为 0。看起来你正在查看的示例代码使用 this.length >>> 0 来确保即使未定义 this.length,也可以确保 len 是数字。

对于许多人来说,位运算不太清晰(Douglas Crockford/jslint 建议不要使用这些东西)。这并不意味着使用它是错误的,但更有利和熟悉的方法存在,可以使代码更易读。更清晰的确保 len0 的方法是以下两种之一。

// Cast this.length to a number
var len = +this.length;
或者
// Cast this.length to a number, or use 0 if this.length is
// NaN/undefined (evaluates to false)
var len = +this.length || 0; 

1
虽然你的第二个解决方案有时会计算为 NaN。例如 +{} ... 最好将两者结合起来:+length||0 - James
1
this.length 是在数组对象的上下文中,它不能是除了非负整数以外的任何东西(至少在 FF 中),因此这里不可能发生。另外,{} || 1 返回 {},所以如果 this.length 是一个对象,你也不会更好。第一种方法还将 this.length 一元转换为数字,这样可以处理 this.length 是 NaN 的情况。已编辑的响应反映了这一点。 - Justin Johnson
jslint 也会抱怨 var len = +this.length,称其为“加号混淆”。Douglas,你真是太挑剔了! - Bayard Randel
道格拉斯很挑剔。虽然他的论点明智且通常有充分的根据,但他所说的并非绝对真理或教义。 - Justin Johnson

15

>>>是无符号右移位运算符(参见JavaScript 1.5规范的第76页),与>>有所不同,后者是带符号的右移位运算符。

>>>更改了负数移位的结果,因为它在移位时不保留符号位。这样做的后果可以通过解释器的示例来理解:

$ 1 >> 0
1
$ 0 >> 0
0
$ -1 >> 0
-1
$ 1 >>> 0
1
$ 0 >>> 0
0
$ -1 >>> 0
4294967295
$(-1 >>> 0).toString(16)
"ffffffff"
$ "cabbage" >>> 0
0

所以这里的意图可能是获取长度,如果长度未定义或不是整数,则为0,如上面的"cabbage"示例。 我认为在这种情况下可以安全地假设this.length永远不会< 0。 尽管如此,我认为这个示例是一个恶劣的hack,有两个原因:

  1. 使用负数时<<<的行为是副作用,可能不是上面示例中预期的(或可能发生的)。

  2. 代码的意图不明显,正如这个问题的存在证明的那样。

最好的做法可能是使用更易读的内容,除非性能绝对关键:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)

那么... @johncatfish 是正确的吗?这是为了确保 this.length 是非负数? - Anthony
4
-1 >>> 0是否有可能出现?如果是,将其移位为4294967295真的是可取的吗?似乎这会导致循环运行比必要次数还要多一些。 - deceze
@deceze:没有看到this.length的实现,是不可能知道的。对于任何“理智”的实现,字符串的长度永远不应该是负数,但是我们可以认为在一个“理智”的环境中,存在一个this.length属性,它总是返回一个整数。 - fmark
你说 >>> 不保留符号位..好的..那么,我得问一下,在处理负数之前..在任何 >>> 或 >> 转换之前,它们是以二进制补码形式还是有符号整数形式存在的,我们怎么知道呢?顺便说一句,我认为二进制补码可能没有被称为具有符号位..它是有符号表示法的一种替代方法,但是可以确定一个整数的符号。 - barlop

10

两个原因:

  1. >>> 的结果是一个“整数”

  2. undefined >>> 0 = 0(由于JS会尝试将LFS强制转换为数字上下文,因此这也适用于“foo”>>> 0等)

请记住,JS中的数字具有双重内部表示方式。这只是一种基本输入合理性的“快速”方法。

然而,-1 >>> 0(糟糕,可能不是所需的长度!)


0
以下是示例Java代码,解释得很清楚:
int x = 64;

System.out.println("x >>> 3 = "  + (x >>> 3));
System.out.println("x >> 3 = "  + (x >> 3));
System.out.println(Integer.toBinaryString(x >>> 3));
System.out.println(Integer.toBinaryString(x >> 3));

输出如下:

x >>> 3 = 536870904
x >> 3 = -8
11111111111111111111111111000
11111111111111111111111111111000

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接