Я смотрел код из Mozilla, который добавлял метод фильтра в Array, и у него была строка кода, которая меня смутила.
var len = this.length >>> 0;
Я никогда не видел → > , используемого в JavaScript раньше.
Что это и что он делает?
Я смотрел код из Mozilla, который добавлял метод фильтра в Array, и у него была строка кода, которая меня смутила.
var len = this.length >>> 0;
Я никогда не видел → > , используемого в JavaScript раньше.
Что это и что он делает?
Он не просто преобразует не Numbers в Number, он преобразует их в Numbers, которые могут быть выражены как 32-битные беззнаковые ints.
Хотя номера JavaScript - это float с двойной точностью (*), побитовые операторы (<<
, >>
, &
, |
и ~
) определяются в терминах операций над 32-битными целыми числами, Выполнение побитовой операции преобразует число в 32-битный подписанный int, теряя любые дроби и более высокие биты, чем 32, перед выполнением вычислений и последующим преобразованием в число.
Таким образом, выполнение побитовой операции без фактического эффекта, например сдвиг вправо 0 бит >>0
, является быстрым способом округления числа и обеспечения его в 32-битном диапазоне int. Кроме того, оператор triple >>>
после выполнения своей неподписанной операции преобразует результаты своего вычисления в число как целое без знака, а не в целое число со знаком, которое другие делают, поэтому его можно использовать для преобразования негативов в 32- версия с двумя дополнениями как большой номер. Использование >>>0
гарантирует, что у вас есть целое число от 0 до 0xFFFFFFFF.
В этом случае это полезно, потому что ECMAScript определяет индексы Array в терминах 32-битных беззнаковых int. Поэтому, если вы пытаетесь реализовать array.filter
таким образом, чтобы точно дублировать то, что говорит стандарт ECMAScript Fifth Edition, вы должны указать номер в 32-разрядный unsigned int, как это.
(На самом деле мало практической необходимости в этом, так как, надеюсь, люди не будут устанавливать 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
(*: они определены как поведение как float. Меня не удивило бы, если бы какой-то механизм JavaScript фактически использовал ints, когда это было возможно, по соображениям производительности. Но это будет деталь реализации, которую вы бы не получили воспользоваться любым преимуществом.)
Беззнаковый оператор сдвига вправо используется во всех реализациях метода extra extra в Mozilla, чтобы гарантировать, что свойство length
представляет собой неподписанное 32-битное целое число.
Свойство length
объектов массива описано в спецификации как:
Каждый объект Array имеет свойство length, значение которого всегда является неотрицательным целым числом меньше 2 32.
Этот оператор является самым коротким способом его достижения, методы внутри массива используют операцию ToUint32
, но этот метод недоступен и существует в спецификации для целей реализации.
В реализациях экстрасети массивов Mozilla стараются быть ECMAScript 5, посмотрите описание метода Array.prototype.indexOf
(& sect; 15.4.4.14 ):
1. Let O be the result of calling ToObject passing the this value as the argument. 2. Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length". 3. Let len be ToUint32(lenValue). ....
Как вы можете видеть, они просто хотят воспроизвести поведение метода ToUint32
для соответствия спецификации ES5 в реализации ES3, и, как я сказал ранее, беззнаковый оператор сдвига вправо является самым простым способом.
Это беззнаковый правый бит сдвига. Разница между этим и подписанным оператором смещения правого бита заключается в том, что оператор без знака с правом сдвига битов (→ > ) заполняется нулями слева, а оператор подписанный правый бит-сдвиг ( → ) заполняется знаковым битом, сохраняя при этом знак от численного значения при смещении.
Driis достаточно объяснил, что такое оператор и что он делает. Здесь смысл, стоящий за ним/почему он использовался:
Смещение любого направления на 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;
>>>
- это беззнаковый оператор сдвига вправо (см. стр. 76 спецификации JavaScript 1.5) в отличие от >>
, подписанного оператора сдвига справа.
>>>
изменяет результаты смещения отрицательных чисел, потому что не сохраняет знаковый бит при переключении. Последствия этого можно понять, например, у интерпретатора:
$ 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, если длина undefined или не целое число, в соответствии с приведенным выше примером "cabbage"
. Я думаю, в этом случае можно с уверенностью предположить, что this.length
никогда не будет < 0
. Тем не менее, я бы сказал, что этот пример - неприятный хак по двум причинам:
Поведение <<<
при использовании отрицательных чисел, побочный эффект, вероятно, не предназначен (или может произойти) в приведенном выше примере.
Намерение кода не является очевидным, как проверяет существование этого вопроса.
Лучшей практикой является, вероятно, использование чего-то более читаемого, если производительность не является абсолютно критичной:
isNaN(parseInt(foo)) ? 0 : parseInt(foo)
Две причины:
Результат → > является "интегральным"
undefined → > 0 = 0 (поскольку JS будет пытаться принудить LFS к числовому контексту, это будет работать и для "foo" → > 0 и т.д.)
Помните, что числа в JS имеют внутреннее представление double. Это просто "быстрый" способ базового входного рассуждения для длины.
Однако, -1 → > 0 (oops, вероятно, не желаемая длина!)