Как стать автором
Обновить

Комментарии 93

Что насчет кроссбраузерности? Во всех IE корректно работает?
Думаю нет ) Это же ИЕ.
НЛО прилетело и опубликовало эту надпись здесь
~~null;      // => 0
~~undefined; // => 0
~~0;         // => 0
~~{};        // => 0
~~[];        // => 0
~~(1/0);     // => 0
~~false;     // => 0
~~true;      // => 1
~~1.2543;    // => 1
~~4.9;       // => 4
~~(-2.999);  // => -2
НЛО прилетело и опубликовало эту надпись здесь
Важным достоинством «~~» является другой приоритет, который в ряде случаев экономит усилия по расстановке скобок:

( "-3.8"|0 ) === -3;    // true
"-3.8"|0 === -3;        // -3
~~"-3.8" === -3;        // true

Правда, зато «~~» приходится записывать не по порядку операций (наподобие того, как «(x / 1024)|0» означает «сперва поделить, затем отбросить дробную часть»), а спереди.

(А ещё «~~» ничего не означает в asm.js, но за пределами asm.js это не недостаток.)
Я про это писал на Хабре ажно три года назад. Тогда не оценили :-)
Дык оттого и не оценили, что Вы сами тогда, находясь под влиянием замеров скорости работы джаваскриптов, принизили значение своего сообщения, высказали «не ведитесь на короткую крутую запись» и проч.

С тех пор скорости работы движков, JavaScript исполняющих, существенно изменились, причём в лучшую сторону, тогда как средняя скорость пробежки по клавиатуре босыми пальцами рук ничуть не переменилася.

Вывод: предпочитать следует такие приёмы, которые экономят труд программиста, даже если вычислительно они представляются несколько малоэффективными.
блин, век живи — век учись…
НЛО прилетело и опубликовало эту надпись здесь
Не пострадает ли от этого читабельность исходников?


Вот именно.

Для отсекания дробной части в Harmony предложен Math.trunc(). Поэтому пишем полифилл, и вперёд — такое моё мнение.
Стоит упомянуть про подводные камни с большими числами:
4242334634634634523523 | 0; // => 368574464
3234534534 | 0; // => -1060432762


Только помните:
~~"12345678901" // -539222987
"12345678901"|0 // -539222987
"12345678901"*1 // 12345678901
parseInt("12345678901", 10) // 12345678901
Хорошо, что Вы напомнили про это, но стоило бы ещё и упомянуть, почему это так. А так оно потому что согласно стандарту операнды битового оператора приводятся в 32-х битному знаковому числу, и, как следствие,
Результат является знаковым 32-битовым целым.
Кстати об использовании таких штук, по моему гораздо удобнее это писать перед выражением, то есть

   var a = 0 ^ x   
      , b = 0 | x


Тогда сразу видно приведение типов.
Да, и можно использовать как отключатель первой строки при отладке:
if(0|'test_srting_1'||'test_string_2')
	alert('test_2');
if('test_srting_1'||'test_string_2')
	alert('test_1');
это можно и короче.

var debug = false

debug && alert(' hit me!')
ИМХО, стоит всё-таки знать меру в использовании особенностей таких конструкций в JS. Кстати говоря, я более, чем уверен, что у многих программистов на JS есть проблема с тем, чтобы разобраться в чужом JS-коде, в том числе из-за использования таких конструкций. Если вам нужно сократить объем кода, то в современных реалиях намного проще будет использовать тот же Google Closure Compiler и учитывать, как он работает, нежели писать такой «код» самому. Из моего опыта, понятность кода (даже на JS) намного важнее лишних пары килобайт, которые будут «выжаты» путем такой «оптимизации».
Код пишется для людей, а не для интерпретатора же. Нужно писать его так, чтобы его легко было прочитать. Разве что только код пишется специально для конкурса обфускаторов.

Даже в таком слабо типизированном языке с динамической типизацией как javascript в ваших программах вы всё равно знаете, откуда к вам приходят данные и какого типа. От этого уже можете плясать.
// Но иногда удобнее писать
if( ~str.indexOf('sub') ){
}

// чем
if( str.indexOf('sub') != -1 ){
}
А если вам нужно рядом инвертировать условие, и реализовать логику «нет в подстроке», то как вы будете писать?
if (!~str.indexOf('sub')) { }
не?
вообще, если один раз запомнить, то такое легко читается.
Лично меня `!~` пугает. Я всё-таки выражения читаю по большей части слева-направо: «в строке индекс подстроки не равен -1»
Вы правы, я тоже никогда так не писал, а потом стал набирать много кода на jquery и приучился писать так:

if (!~$.inArray(str, arr)) { }

согласитесь, тут больше смысла. хотя да, пугает иногда.
Вы используете библиотеку для поиска вхождения одной строки в другую?
Теперь я понимаю кто такой «программист на jquery»
«$.inArray()» — это не поиск подстроки в строке, это поиск элемента в массиве.
Странный вопрос и выводы.

Я не использую библиотеку для поиска вхождений одной строки в другую. С чего вы взяли? В коде написан поиск строки в массиве. Но если вам интересно, то и так я уже давно не пишу. Просто добавляю к проекту SugarJS и использую стандартный indexOf(). Но раньше, когда моё бывшее начальство краснело от идеи изменения прототипов, мне приходилось для поиска внутри массива использовать jQuery, чтобы это везде работало. (до 9 версии IE её не поддерживал). А поиск строки в строке вроде очень давно во всех браузерах реализован, так что использовал его, ну уж точно не $.inArray(), акститесь.
ясно, я неверно вас понял
да и не знал, что с indexOf для Array у ie была беда
у меня аж руки затряслись когда прочитал что indexOf() до 9 ИЕ не поддерживался. shift+ctrl+F по проекту — 189 вхождений, ПАНИКА!
Работает все в восьмом, напугали блин.
Что это, Бэрримор?
В смысле зачем? Расширить прототип я и сам могу.
А indexOf работает начиная с IE8, что собственно и требуется.
Чтобы не беспокоится ни о чем и ВНЕЗАПНО не бросаться проверять код на «а вдруг я использую фишку из es6 которую {browser} не поддерживает».
Просто вы так панически среагировали :)
Ааа, ну в моем случае это мертвому припарки. Нашего монстра проще пристрелить чем хоть как-то покрыть тестами.
С 0 выйдет конфуз у вас
А, это у меня конфуз вышел, contains() же :-S
Ну, contains.
Похоже, есть смысл говорить «вне Firefox 19 конфуз выйдет».
Согласен, но на участках с очень большим количеством итераций, лучше перестраховаться.
+ Это не кроссбраузерно, а ломать голову, расширен ли прототип на проекте не охота.
+ Это не кроссбраузерно, а ломать голову, расширен ли прототип на проекте не охота.

Как это не кроссбраузерно? А вы ломаете голову, подключён ли у вас jQuery, объявлена ли функция foo или bar при её вызове? Метод contains точно такой же, как любой другой. Или у вас есть этот метод или нету.

Согласен, но на участках с очень большим количеством итераций, лучше перестраховаться.

Этот аргумент вообще не понял. По-моему нету ничего более очевидного, чем слово contains — это самая лучшая перестраховка.
Насчёт кросс-браузерности полезна библиотека Underscore.string и запись «_.str.include(str, 'sub')».
Chrome 25 — www.rubaxa.org/screenshot/6b15eb6a74e217165b52df651dd2.png

И да, когда я разрабатываю код, который будет работать в неизвестном мне месте, стараюсь писать так, чтобы понял и IE6. + Даже подключенный shim будет на 10 порядково медленней.
Спичечные оптимизации? Может, вы функции вообще не пишете, а сразу инлайните код, потому что так быстрее?
В браузере — да, под V8 — реальный прирост производительности. + если рассматривать старые браузеры, то там это играет значимую роль. Всё зависит от количества таких мест.
за if (~arr.indexOf(val)) { /* ... */ } на меня косо смотрят, а вот за такое наверное вообще убьют :)
А шутка с!!! прокатывает?
Пожалуйста, внимательно прочитывайте блогозапись до конца перед сочинением комментариев к ней. Вы пропустили характеристику «удобное средство отбрасывания дробной части».
Простите меня.
Охотно прощаю.
Но прошу заменить, в статье не написано, что `(x + .5)| 0` по скорости быстрей Math.round (если рассматривать синтетический тест).
А потом кто-то после вас застрянет на этом месте и будет думать: «а что же имел в виду автор?». Данная конструкция заметно ухудшает читабельность кода.
Указанный метод быстрее, но оба округления настолько быстры, что экономить стоит только в тех случаех когда у вас миллионы округлений в секунду.

P.S.
Спасибо за тесты.

В Файерфоксе они показывают одинаковые значения скорости того и другого округления.
Добавил еще:
Chrome 25 vs FF 19 vs IE 10
image
Друзья, я просто пытаюсь показать, что данная практика имеет свои предпосылки, а не только из-за короткой записи. Писав маленькое приложение под iPhone где приходилось работать с пикселями (canvas) такие микро оптимизации помогали. Это всё было 3 года назад, надеюсь сейчас ситуация изменилась.
Да, но вы же не указали, что это микро оптимизации. Кто-то решит «вау, круто» и начнет везде лепить подобную конструкцию.
Ведь изначально, код надо писать понятным, а оптимизировать уже по мере нужды.
тест этот совсем не замеряет скорости округления, потому что loop invariant code motion выносит инвариант цикла за цикл.

замеряется по сути дела скорость кручения пустого цикла.

почитайте: mrale.ph/blog/2012/12/15/microbenchmarks-fairy-tale.html
Спасибо, хорошая статья. Тест и вправду не показателе, но как я уже говорил ранее, это реальная практика, которая давала эффект в JS/ActionScript, 3 года назад точно.
Конструкция Math.floor() гораздо очевидней, чем |0. Это уже повод не использовать второй путь, даже без учета сообщений выше про проблемы с большими числами. И вообще, не зря битовые операции по умолчанию запрещены в jslint и jshint
До известной степени это впечатление справедливо.

Я замечу, однако, что программист до известной степени способен воздействовать на собственное впечатление от «|0» при помощи мнемотехники, то есть мысленно связать этот оператор с некоторым запоминающимся символом с той целью, чтобы надёжнейше обеспечить себе его очевидность, причём надолго.

На ум приходит, например, идея рассматривать вертикальную черту как острый и безжалостный нож гильотины, отсекающий последующую цифру — что метафорически символизирует отсечение всей дробной части числа. Такой символ очень неплохо передаёт всю суть происходящего — не хуже, чем английское слово «floor» передаёт суть названного им метода.
НЛО прилетело и опубликовало эту надпись здесь
Вдругорядь

куда-куда?)
Mithgol
Да ладно, вы вот слово «патч» упоминаете и ничего (я наскоро посмотрел ваши комментарии), совершенно нерусское, корявое слово, а «вкруголядь» хоть русское.
но, блин, такое сложное.
Эта конструкция делает следующее: объект.toString().перевод_в_integer, если_это_число_иначе_в_ноль. Сразу становится понятно почему массив из двух элементов преобразуется в ноль. Потому что [1,2].toString() это «1,2».
Бесконечность?
Может кто-нибудь объяснить зачем она нужна и как ее использовать? Чес слово не могу придумать.
А что возвращать при 1/0?
Синий экран.
> не округляет, а именно отбрасывает дробную часть
Раньше это называлось округлением вниз по модулю. Да, есть разные способы окрулять.
НЛО прилетело и опубликовало эту надпись здесь
Ну дык это и не магия — а просто несколько нестандартное использование привычного инструмента.
Так, например ''+x всегда возвращает строку, +x — число, !!x — булевское значение. Тоже, казалось бы, ничего магического — но при этом удобная штука для быстрого приведения типов.
(вдруг кто не знал)

Из подобных вещей я чаще всего использую || для обработки undefined и null / false значений:

function GetRow (index)
{
  return $("selector").eq (index || 0);
}


Аналогично для строк:
$("selector").val (n || "")


Особенно это актуально когда конструируем data для ajax запроса, т.к. там если в поле передадим «undefined», то на сервер это в null не переведёт и будет ругаться (по крайней мере для MVC2 это так).
оператор || возвращает первый положительный вариант или последний из условия, если все остальные дают false

То есть вы можете писать и такие конструкции
> null || 0 // => 0
> null || 0 || 1 // =>1
> null || 1 || 0 // => 1
> null || 0 || 0 // => 0
> null || 0 || null // => null
> null || 0 || null || undefined // => undefined
> null || 0 || '' || [] || undefined // => []
> null || 0 || '' || false || undefined // => undefined
> null || 0 || '' || false || undefined || 1 // => 1
> null || 0 || '' || false || 1 || undefined // => 1
Я это нарочно.

Я опасался вот чего: если думать про «|0» в терминах «побитовое ИЛИ с нулём», то этот оператор воспринимается в качестве NOP-подобной «мусорной команды», потому что из курса двоичной арифметики каждому известно, что побитовое ИЛИ с нулём не меняет значение другого (ненулевого) операнда.

Роль этого оператора в джаваскрипте определяется правилами предварительного преобразования операнда к целому числу, а не смыслом самóй операции. Но мне не хотелось упоминать и об этом: смысл статьи сильнее дойдёт до читателя, если читатель сам проделает часть пути к его разгадке.
Воистину эти слова почти цитатой можно (нужно) запостить апдейтом :)
А я вот думаю, что не нужно, причём по тем же соображениям.
Вы должны учитывать то что в этом тесте не учитывается оверхед на вызов функции Math.round
Вот подобный, но учитывающий вызовы: jsperf.com/math-round-vs-x-0-5-0/2
Но в том и смысл, что это и не нужно выносить в функцию, а можно использовать инлайн. И в предыдущем тесте сравнивает прирост по сравнению с floor, а не с round
Просто так

Python 2.7.3 |EPD_free 7.3-2 (32-bit)| (default, Apr 12 2012, 14:30:37) [MSC v.1500 32 bit (Intel)] on win32
Type «copyright», «credits» or «license()» for more information.
>>> 3|0
3
>>> 3.8|0

Traceback (most recent call last):
File "<pyshell#1>", line 1, in 3.8|0
TypeError: unsupported operand type(s) for |: 'float' and 'int'
>>> -3|0
-3
>>> '3'|0

Traceback (most recent call last):
File "<pyshell#3>", line 1, in '3'|0
TypeError: unsupported operand type(s) for |: 'str' and 'int'
>>>
А чего вы еще ожидали, питон строго типизирован.
Вдругорядь я заметил конструкцию «вдругорядь», и тогда глаза мои открылись, и я увидел, что это Mithgol
ааа, мои глаза…
После этого вы говорите, что код Perl вам трудно понять?
:)
Вроде уж совсем основы.
n|0 или n>>0 выполнят преобразование ToInt32, n>>>0 — ToUInt32, для отбрасывания дробной части использовать аккуратно — с большими числами будут проблемы — (Math.pow(2,31)|0)===-2147483648.
Т.к. внутренние операции преобразования типов наружу не вынесены, приходится крутиться
function ToInt32(val){
  return val>>0}
function ToUInt32(val){
  return val>>>0}
function ToUInt16(val){
  return (val>>>0)%65536}
var ToInteger=Number.toInt||Number.toInteger||function(val){
  return val=+val,val!==val?0:val!==0&&val!==Infinity&&val!==-Infinity?(val>0||-1)*Math.floor(Math.abs(val)):val}
Кроме Math.floor есть ещё round и ceil, также существует parseInt, и все они работают в несколько раз быстрее у меня сейчас в хроме.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории