什么是 JavaScript >>> 运算符以及如何使用它?

IT技术 javascript operators bit-shift
2021-01-09 16:47:19

我正在查看来自 Mozilla 的代码,该代码将过滤器方法添加到 Array,它有一行代码让我感到困惑。

var len = this.length >>> 0;

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

6个回答

它不仅将非数字转换为数字,还将它们转换为可以表示为 32 位无符号整数的数字。

虽然JavaScript的数字是双精度浮点(*),位运算符(<<>>&|~)在对32位整数运算的定义。执行按位运算会将数字转换为 32 位有符号整数,丢失任何分数和高于 32 的位,然后再进行计算然后转换回数字。

因此,执行没有实际效果的按位运算(例如右移 0 bits >>0)是一种快速舍入数字并确保其在 32 位 int 范围内的方法。此外,三元>>>运算符在执行其无符号运算后,将其计算结果转换为 Number 作为无符号整数,而不是其他运算符所做的有符号整数,因此它可用于将负数转换为 32 位二进制补码版本作为一个大数字。使用>>>0确保您有一个介于 0 和 0xFFFFFFFF 之间的整数。

在这种情况下,这很有用,因为 ECMAScript 根据 32 位无符号整数定义数组索引。因此,如果您试图以array.filter完全复制 ECMAScript 第五版标准所说的方式实现,您可以像这样将数字转换为 32 位 unsigned int。

(实际上几乎没有实际需要,因为希望人们不会设置array.length0.5, -1,1e21'LEMONS'。但这是我们谈论的 JavaScript 作者,所以你永远不知道......)

概括:

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 深度描述和表格,-1 因为 array.length 验证自身并且不能任意设置为不是整数或 0 的任何内容(FF 抛出此错误:)RangeError: invalid array length
2021-03-23 16:47:19
但是,规范故意允许在非 Array 上调用许多 Array 函数(例如 via Array.prototype.filter.call),因此array实际上可能不是真正的Array:它可能是其他一些用户定义的类。(不幸的是,它不能可靠地是一个 NodeList,这是你真正想要这样做的时候,因为它是一个宿主对象。这使得你唯一可以真正做到这一点的地方是arguments伪数组。)
2021-03-27 16:47:19
很好的解释和很好的例子!不幸的是,这是 Javascript 的另一个疯狂方面。我只是不明白当你收到错误的类型时抛出错误有什么可怕的。允许动态类型而不允许每个意外错误创建类型转换是可能的。:(
2021-03-27 16:47:19
" 使用 >>>0 可确保您得到一个介于 0 和 0xFFFFFFFF 之间的整数。" 会是什么样的if声明看试图确定评价的左侧是不是int时喜欢这个?'lemons'>>>0 === 0 && 0 >>>0 === 0评价为真的?即使柠檬显然是一个词..?
2021-04-08 16:47:19

Mozilla的所有array extra方法实现中都使用了无符号右移运算符,以确保该length属性是一个无符号的 32 位整数

length数组对象属性在规范中描述为:

每个 Array 对象都有一个 length 属性,其值始终是一个小于 2 32的非负整数

此运算符是实现它的最短方法,内部数组方法使用该ToUint32操作,但该方法不可访问且存在于规范中以用于实现目的。

Mozilla数组 extras实现尝试与ECMAScript 5兼容,请查看该Array.prototype.indexOf方法的描述(第 15.4.4.14 节):

1.让O为调用ToObject传递this值的结果 
   作为论据。
2.令lenValue为调用O的[[Get]]内部方法的结果 
   参数“长度”。
3. 令 len 为ToUint32(lenValue)
....

如您所见,他们只是想ToUint32在 ES3 实现上重现该方法的行为以符合 ES5 规范,而且正如我之前所说,无符号右移运算符是最简单的方法。

虽然链接数组额外的实现可能是正确的(或接近正确的),但代码仍然是一个糟糕的代码示例。也许即使是澄清意图的评论也能解决这种情况。
2021-03-11 16:47:19
@Marcel:请记住,大多数Array.prototype方法都是有意为泛型的,它们可以用于类似数组的对象,例如Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. arguments对象也是一个很好的例子。对于数组对象,不可能改变length属性的类型,因为它们实现了一个特殊的[[Put ]]内部方法,当对length属性进行赋值时,再次进行转换ToUint32等动作,比如上面的删除索引新的长度...
2021-03-21 16:47:19
数组的长度可能不是整数吗?我无法想象,所以这种ToUint32对我来说似乎有点不必要。
2021-03-23 16:47:19

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

@Ivan,请参阅下面贾斯汀的回答。它实际上是一种确保 len 变量包含数字的方法。
2021-03-22 16:47:19
this.length >>> 0 将有符号整数转换为无符号整数。就个人而言,我发现这在加载包含无符号整数的二进制文件时很有用。
2021-03-23 16:47:19
伊万,那会将它移动 0 个位置;该声明不会改变任何事情。
2021-03-29 16:47:19
@Ivan,通常,我会说将值移动零位绝对没有意义。但这是 Javascript,所以它背后可能有某种含义。我不是 Javascript 专家,但它可能是一种确保值实际上是无类型 Javasacript 语言中的整数的方法。
2021-04-04 16:47:19
此外,>>>转换为整数,而一元+不这样做。
2021-04-07 16:47:19

Driis已经充分解释了操作符是什么以及它做什么。这是它背后的含义/为什么使用它:

将任何方向移动 by0确实会返回原始数字并将转换null0您正在查看的示例代码似乎this.length >>> 0用于确保len即使this.length未定义也是数字

对于许多人来说,按位运算是不清楚的(Douglas Crockford/jslint 建议不要使用这样的东西)。这并不意味着这样做是错误的,而是存在更有利和更熟悉的方法来使代码更具可读性。确保这len一点的更明确方法0是以下两种方法之一。

// 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; 
jslint 也会抱怨 var len = +this.length 作为“令人困惑的优点”。道格拉斯,你太挑剔了!
2021-03-10 16:47:19
道格拉斯很挑剔。虽然他的论点是明智的,而且通常是有根据的,但他所说的既不是绝对的,也不是福音。
2021-03-10 16:47:19
this.length 在数组对象的上下文中,除了非负整数(至少在 FF 中)之外不能是任何东西,所以这里不可能。另外,{} || 1 返回 {} 所以如果 this.length 是一个对象,你也不会更好。在第一种方法中一元转换 this.length 的好处是它可以处理 this.length 为 NaN 的情况。编辑回复以反映这一点。
2021-03-17 16:47:19
虽然,您的第二个解决方案有时会评估为NaN.. 例如+{}... 最好将两者结合起来:+length||0
2021-03-24 16:47:19

>>>无符号右移运算符请参阅 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)
这种情况-1 >>> 0是否会发生,如果是这样,是否真的需要将其转换为 4294967295?似乎这会导致循环比必要的多运行几次。
2021-03-11 16:47:19
你说 >>> 不保留符号位..好吧..所以,我不得不问,当我们处理负数时..在任何 >>> 或 >> 转换之前,它们是否在 2s 中形式,或者它们是有符号整数形式,我们怎么知道?顺便说一句,我认为 2s 补码可能没有符号位..它是有符号表示法的替代方法,但可以确定整数的符号
2021-03-16 16:47:19
@deceze:没有看到this.length的实现是不可能知道的。对于任何“理智”的实现,字符串的长度永远不应该是负数,但是有人可能会争辩说,在“理智”的环境中,我们可以假设存在一个this.length总是返回整数属性。
2021-04-01 16:47:19
Sooo ... @johncatfish 是正确的吗?是为了确保 this.length 是非负的?
2021-04-07 16:47:19