Что такое оператор JavaScript >>> и как вы его используете?

150

Я искал код из Mozilla, который добавляет метод фильтра в Array, и в нем была строка кода, которая смутила меня.

var len = this.length >>> 0;

Я никогда не видел >>> используется в JavaScript раньше.
Что это такое и что оно делает?

Кеннет Дж
источник
@CMS Правда, этот код / ​​вопрос исходит от тех; однако, ответ здесь более конкретен и ценен, чем предыдущий.
Джастин Джонсон
2
Или это ошибка, или парни из Mozilla предполагают, что this.length может быть -1. >>> - оператор смещения без знака, поэтому var len всегда будет 0 или больше.
user347594
1
Эш Сирл нашел в этом применение - отменил реализацию лорда JS (Дуга Крокфорда) на Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (хотя он делал тесты, ха-ха).
Дан Бим

Ответы:

212

Он не просто конвертирует не-числа в число, он конвертирует их в числа, которые могут быть выражены как 32-разрядные целые числа без знака.

Хотя Числа JavaScript являются двойной точности поплавки (*), операторы битовые ( <<, >>, &, |и ~) определены в терминах операций на 32-разрядных целых чисел. Выполнение побитовой операции преобразует число в 32-разрядное целое число со знаком, теряя все дроби и старшие разряды, чем 32, перед выполнением вычисления и последующим преобразованием обратно в число.

Таким образом, выполнение побитовой операции без реального эффекта, например, сдвиг вправо на 0 бит >>0, - это быстрый способ округлить число и убедиться, что оно находится в 32-битном диапазоне int. Кроме того, тройной >>>оператор после выполнения своей операции без знака преобразует результаты своего вычисления в число как целое число без знака, а не как целое число со знаком, как это делают другие, поэтому его можно использовать для преобразования отрицаний в дополнение 32-бит-два. Версия как большой номер. Использование >>>0гарантирует, что у вас есть целое число от 0 до 0xFFFFFFFF.

В этом случае это полезно, потому что ECMAScript определяет индексы массива в терминах 32-битных беззнаковых целых. Поэтому, если вы пытаетесь реализовать array.filterтаким образом, который бы точно повторял то, что говорится в стандарте ECMAScript Fifth Edition, вы бы преобразовали число в 32-битное целое число без знака, как это.

(На самом деле существует мало практическая потребность в этом , как мы надеемся , что люди не будут установки array.lengthна 0.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 действительно использует int, когда это возможно, по соображениям производительности. Но это будет деталь реализации, которую вы не захотите взять. преимущество.)

bobince
источник
2
+2 в подробном описании и таблице, -1, потому что array.length проверяет себя и не может быть произвольно установлен на что-либо, что не является целым числом или 0 (FF выдает эту ошибку:) RangeError: invalid array length.
Джастин Джонсон
4
Однако спецификация преднамеренно позволяет вызывать многие функции Array для не-Array (например, через Array.prototype.filter.call), поэтому на arrayсамом деле может не быть действительным Array: это может быть какой-то другой определенный пользователем класс. (К сожалению, это не может быть надежным NodeList, когда вы действительно захотите это сделать, поскольку это хост-объект. Это оставляет единственное место, где вы действительно можете сделать это, как argumentsпсевдо-массив.)
bobince
Отличное объяснение и отличные примеры! К сожалению, это еще один безумный аспект Javascript. Я просто не понимаю, что такого ужасного в выдаче ошибки, когда вы получаете неправильный тип. Можно разрешить динамическую типизацию, не допуская при каждой случайной ошибке создания приведения типов. :(
Майк Уильямсон
«Использование >>> 0 гарантирует, что у вас есть целое число от 0 до 0xFFFFFFFF». как бы ifвыглядело это утверждение при попытке определить, что левая часть оценки не является целой? 'lemons'>>>0 === 0 && 0 >>>0 === 0оценивает как правда? хотя лимоны, очевидно, слово ..?
Zze
58

Оператор сдвига вправо без знака используется во всех реализациях метода extra этого массива в Mozilla, чтобы гарантировать, что lengthсвойство представляет собой 32-разрядное целое число без знака .

lengthСвойство объектов массива будет описано в описании как:

Каждый объект Array имеет свойство length, значение которого всегда является неотрицательным целым числом, меньшим 2 32 .

Этот оператор является кратчайшим путем для достижения этого, методы внутреннего массива используют ToUint32операцию, но этот метод недоступен и существует в спецификации для целей реализации.

Реализации дополнительных функций в массиве Mozilla стараются быть совместимыми с ECMAScript 5 , посмотрите описание Array.prototype.indexOfметода (§ 15.4.4.14):

1. Пусть O будет результатом вызова ToObject с передачей значения this 
   в качестве аргумента.
2. Пусть lenValue будет результатом вызова внутреннего метода [[Get]] для O с 
   аргумент "длина".
3. Пусть len будет ToUint32 (lenValue) .
....

Как вы можете видеть, они просто хотят воспроизвести поведение ToUint32метода в соответствии со спецификацией ES5 для реализации ES3, и, как я уже говорил, оператор беззнакового сдвига вправо является самым простым способом.

CMS
источник
Хотя реализация дополнений в массиве может быть верной (или близкой к правильной), код все еще является плохим примером кода. Возможно, даже комментарий для уточнения намерения разрешит эту ситуацию.
Fmark
2
Возможно ли, что длина массива не является целым числом? Я не могу себе этого представить, так что ToUint32мне это немного не нужно.
Марсель Корпель
7
@Marcel: Имейте в виду, что большинство Array.prototypeметодов являются преднамеренно общими , их можно использовать в объектах, подобных массивам, например Array.prototype.indexOf.call({0:'foo', 1:'bar', length: 2}, 'bar') == 1;. argumentsОбъект также является хорошим примером. Для объектов чистого массива невозможно изменить тип lengthсвойства, потому что они реализуют специальный внутренний метод [[Put ]], и когда присваивается lengthсвойство свойству, он снова преобразуется ToUint32и выполняются другие действия, такие как удаление индексов выше новая длина ...
CMS
32

Это оператор беззнакового сдвига вправо . Разница между этим и подписанным правом оператора сдвига бит , является то , что без знака правый бит - оператор сдвига ( >>> ) заполняется нулями слева, и подписал правый бит оператор сдвига ( >> ) заполняется знаковый бит, таким образом , сохранение знака числового значения при смещении.

driis
источник
Иван, что бы сдвинуть его на 0 мест; это утверждение ничего не изменит.
декан J
3
@ Иван, обычно, я бы сказал, что смещение значения на ноль мест абсолютно бессмысленно. Но это Javascript, поэтому за этим может быть смысл. Я не гуру Javascript, но это может быть способ убедиться, что значение на самом деле является целым числом в языке Javasacript без типов.
Дрис
2
@ Иван, см. Ответ Джастина ниже. Фактически это способ гарантировать, что переменная len содержит число.
Дрис
1
Далее, >>>конвертирует в целое число, которое +не делает унарное.
рекурсивный
this.length >>> 0 преобразует целое число без знака в целое число без знака. Лично я нашел это полезным при загрузке двоичного файла с беззнаковыми целыми.
Мэтт Паркинс
29

Дриис достаточно объяснил, что такое оператор и что он делает. Вот смысл этого / почему он был использован:

Сдвиг любого направления на 0действительно возвращает исходное число и приведёт nullк 0. Похоже, что пример кода, который вы просматриваете, используется this.length >>> 0для обеспечения того, чтобы lenон был числовым, даже если this.lengthон не определен.

Для многих людей побитовые операции неясны (и Дуглас Крокфорд / 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; 
Джастин Джонсон
источник
1
Хотя ваше второе решение иногда оценивается как NaN... Например +{}... Лучше всего объединить два:+length||0
Джеймс
1
this.length находится в контексте объекта массива, который не может быть ничем, кроме неотрицательного целого числа (по крайней мере, в FF), поэтому здесь это невозможно. Кроме того, {} || 1 возвращает {}, так что вам не лучше, если this.length - объект. Преимущество одинарного приведения this.length в первом методе состоит в том, что он обрабатывает случаи, когда this.length равен NaN. Отредактированный ответ, чтобы отразить это.
Джастин Джонсон
jslint также будет жаловаться на var len = + this.length как «запутанные плюсы». Дуглас, ты такой придирчивый!
Баярд Рандель
Дуглас разборчив. И хотя его аргументы мудры и типично обоснованы, то, что он говорит, не является ни абсолютным, ни евангельским.
Джастин Джонсон
15

>>>это беззнаковый оператор сдвига вправо ( см. 76 1.5 спецификации JavaScript ), в отличие от >>, с подписью правильного оператора сдвига.

>>>изменяет результаты сдвига отрицательных чисел, потому что он не сохраняет знаковый бит при сдвиге . Последствия этого можно понять на примере интерпретатора:

$ 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. Тем не менее, я бы сказал, что этот пример - мерзкий взлом по двум причинам:

  1. Поведение <<<при использовании отрицательных чисел, побочный эффект, вероятно, не предназначен (или может произойти) в примере выше.

  2. Намерение кода не очевидно , поскольку существование этого вопроса подтверждает.

Лучше всего использовать что-то более читабельное, если производительность не является абсолютно критичной:

isNaN(parseInt(foo)) ? 0 : parseInt(foo)
fmark
источник
Ооо ... @johncatfish правильно? Это для того, чтобы эта длина была неотрицательной?
Энтони
4
Может ли -1 >>> 0когда-нибудь случиться случай , и если да, то действительно ли желательно изменить его на 4294967295? Похоже, это приведет к тому, что цикл будет запущен несколько раз, чем необходимо.
deceze
@deceze: не видя реализации this.lengthэтого невозможно узнать. Для любой «нормальной» реализации длина строки никогда не должна быть отрицательной, но тогда можно утверждать, что в «нормальной» среде мы можем предположить существование this.lengthсвойства, которое всегда возвращает целое число.
Fmark
Вы говорите, что >>> не сохраняет бит знака ... хорошо .. Итак, я должен спросить, когда мы имеем дело с отрицательными числами ... перед любым преобразованием >>> или >> они в 2s Compliement форма, или они в подписанной целочисленной форме, и как бы мы узнали? Между прочим, дополнение 2s, я думаю, возможно, не имеет знакового бита ... это альтернатива знаковой записи, но можно определить знак целого числа
barlop
10

Две причины:

  1. Результатом >>> является «интеграл»

  2. undefined >>> 0 = 0 (поскольку JS попытается привести LFS к числовому контексту, это также будет работать для "foo" >>> 0 и т. д.)

Помните, что числа в JS имеют внутреннее представление double. Это просто «быстрый» способ ввода здравомыслия в длину.

Тем не менее , -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
nitinsridar
источник