Що таке оператор JavaScript >>> і як ним користуватися?


150

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

var len = this.length >>> 0;

Я ніколи не бачив, щоб раніше >>> використовувався в JavaScript.
Що це таке і що він робить?


@CMS Щоправда, цей код / ​​питання походить з цих питань; однак відповіді тут більш конкретні та цінні, ніж попередні.
Джастін Джонсон

2
Або це помилка, або хлопці Mozilla припускають, що це може бути -1. >>> є оператором зміни без підпису, тому зміна завжди буде 0 або більше.
користувач347594

1
Еш Серл знайшов для цього свою користь - перекинувши лорда на реалізацію JS (Doug Crockford) на Array.prototype.push/ Array.prototype.pop- hexmen.com/blog/2006/12/push-and-pop (хоча він робив тести, ха-ха).
Dan Beam

Відповіді:


211

Він не просто конвертує нечислові числа, а перетворює їх у числа, які можуть бути виражені як 32-бітні неподписані вставки.

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

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

У цьому випадку це корисно, оскільки ECMAScript визначає індекси масиву в термінах 32-бітових неподписаних ints. Отже, якщо ви намагаєтесь реалізувати array.filterтаким чином, що точно дублює те, що говорить стандарт ECMAScript Fifth Edition, ви додасте число до 32-бітного неподписаного 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

(*: ну, вони визначаються як поводяться як плавучі. Мене не здивувало б, якщо якийсь механізм JavaScript насправді використовував ints, коли це можливо, з міркувань продуктивності. Але це буде детальною інформацією про реалізацію, яку ви не отримаєте. перевага.)


2
+2 в описі глибини та таблиці, -1 тому, що array.length перевіряє себе і не може бути довільно встановлено на що-небудь, що не є цілим чи 0, (FF видає цю помилку:) RangeError: invalid array length.
Джастін Джонсон

4
Однак специфікація свідомо дозволяє викликати багато функцій масиву в не-масиві (наприклад, через Array.prototype.filter.call), тому arrayнасправді це не може бути реально Array: це може бути якийсь інший визначений користувачем клас. (На жаль, він не може надійно бути NodeList. Це коли ви дійсно хочете це зробити, оскільки це хост-об'єкт. Це залишає єдине місце, де ви реально це зробите як argumentsпсевдо-масив.)
bobince

Прекрасне пояснення та чудові приклади! На жаль, це ще один шалений аспект Javascript. Я просто не розумію, що таке жахливе у тому, щоб кинути помилку, коли ви отримаєте неправильний тип. Можна дозволити динамічне введення тексту, не дозволяючи кожній випадковій помилці створювати кастинг типу. :(
Майк Вільямсон

"Використання >>> 0 гарантує отримання цілого числа між 0 і 0xFFFFFFFF." як ifвиглядає це твердження для цього, намагаючись визначити, що ліва сторона оцінки не є цілою? 'lemons'>>>0 === 0 && 0 >>>0 === 0оцінює як істинне? хоч лимони, очевидно, слово ..?
Zze

58

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

lengthВластивість об'єктів масиву буде описано в описі як:

Кожен об’єкт масиву має властивість довжини, значення якого завжди є неотрицательним цілим числом менше 2 32 .

Цей оператор є найкоротшим способом його досягнення, внутрішні методи масиву використовують ToUint32операцію, але цей метод недоступний і існує в специфікації для цілей реалізації.

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

1. Нехай O є результатом виклику ToObject, передаючи це значення 
   як аргумент.
2. Нехай lenValue є результатом виклику внутрішнього методу O з [[Get]] 
   аргумент "довжина".
3. Нехай len буде ToUint32 (lenValue) .
….

Як ви бачите, вони просто хочуть відтворити поведінку ToUint32методу на відповідність специфікації ES5 щодо впровадження ES3, і, як я вже говорив раніше, не підписаний правий оператор зміни є найпростішим способом.


Хоча реалізація додаткових додатків масиву може бути правильною (або близькою до правильної), код все ще є невірним прикладом коду. Можливо, навіть коментар для уточнення наміру вирішив би цю ситуацію.
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

Це непідписаний правий оператор зсуву бітів . Різниця між цим та підписаним оператором зсуву правого біта полягає в тому, що непідписаний правий оператор зсуву бітів ( >>> ) заповнює нулі зліва, а підписаний оператор зсуву правого біта ( >> ) заповнює біт знака, таким чином збереження знака числового значення при зміщенні.


Іване, що змістить його на 0 місця; це твердження нічого не змінить.
Дін J

3
@Ivan, як правило, я б сказав, що зміщення значення на нульові місця абсолютно не має сенсу. Але це Javascript, тому за цим може бути сенс. Я не є гуру Javascript, але це може бути спосіб переконатися, що значення насправді є цілим числом в безтиповій мові Javasacript.
driis

2
@Ivan, дивіться відповідь Джастіна нижче. Насправді це спосіб переконатися, що змінна len містить число.
driis

1
Далі >>>перетворюється на ціле число, яке унар +не робить.
рекурсивний

this.length >>> 0 перетворює підписане ціле число в неподписане. Особисто я вважаю це корисним під час завантаження бінарного файлу з неподписаними в ньому вписами.
Метт Паркінс

29

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; 

1
Хоча, ваше друге рішення іноді оцінюватиме, NaNнаприклад +{}... Напевно, найкраще поєднати це:+length||0
Джеймс

1
this.length знаходиться в контексті об’єкта масиву, який не може бути нічим іншим, як негативним цілим числом (принаймні, у FF), тому тут це неможливо. Також {} || 1 повертається {}, тож вам не краще, якщо this.length є об’єктом. Перевага також уніарного лиття this.length у першому методі полягає в тому, що він обробляє випадки, коли this.length становить NaN. Відредагована відповідь, щоб це відобразити.
Джастін Джонсон

jslint скаржиться на var len = + this.length також як на "заплутані плюси". Дуглас, ти такий вибагливий!
Байард Рендел

Дуглас прискіпливий. І хоча його аргументи мудрі і, як правило, обґрунтовані, те, що він каже, не є абсолютним і не євангельським.
Джастін Джонсон

15

>>>- це непідписаний правий оператор зміни ( див. стор. 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, якщо довжина не визначена або не є цілим числом, як показано у "cabbage"прикладі вище. Я думаю, що в цьому випадку можна впевнено припустити, що this.lengthцього ніколи не буде < 0. Тим не менш, я заперечую, що цей приклад - це неприємний злом з двох причин:

  1. Поведінка <<<при використанні негативних чисел, побічний ефект, ймовірно, не призначений (або може виникнути) у наведеному вище прикладі.

  2. Намір коду не очевидний , оскільки підтверджується наявність цього питання.

Найкраща практика, ймовірно, використовувати щось більш читабельне, якщо продуктивність не є абсолютно критичною:

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

Sooo ... @johncatfish правильний? Це переконатися, що this.length не є негативною?
Ентоні

4
Чи може випадок -1 >>> 0коли-небудь траплятися, і якщо так, чи дійсно бажано перевести його на 4294967295? Здається, що це призведе до запуску циклу в кілька разів більше, ніж потрібно.
деге

@deceze: Не бачачи реалізації this.lengthцього, неможливо знати. Для будь-якої "розумної" реалізації довжина рядка ніколи не повинна бути негативною, але тоді можна стверджувати, що в "розумному" середовищі ми можемо припустити існування this.lengthвластивості, яка завжди повертає цілісне число.
fmark

Ви кажете, що >>> не зберігає біт знака .. Добре. Отже, мені доведеться запитати, коли ми маємо справу з від'ємними числами ... перед будь-яким переходом >>> чи >>, чи відповідають вони за 2-х секунд форми, чи вони у підписаній цілочисельній формі, і як би ми це знали? До речі, доповнення 2s, я думаю, можливо, не сказано, що має бітний знак .. це альтернатива підписаному позначенню, але можна визначити знак цілого числа
барлоп

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.