Комментарии 149
Метки можно назначать циклам for или блокам кода в JS… А вы не знали? Я — так точно этого не знал. Затем на эти метки можно ссылаться и использовать команды break или continue в циклах for, а также применять команду break в блоках кода.
Прям эволюция сишного
goto
.JS напоминает в некоторых элементах Perl
Я не говорю, что это нужно постоянно такое использовать. Я говорю о тех редких случаях, когда по-другому будет на уровне «почесть левое ухо ногой правой пятки».
Continue label посреди кода конечно же не является ub, однако без диагностических ошибок можно попасть не совсем туда, куда ожидалось. Прыжок из/в области видимости switch может быть настолько же неожиданными как и 0x873162387 вместо 0х0 в неинициализированном поле структуры — не все знакомы с текстом стандартов языка и помнят все нюансы из него.
LOOP:
for {
select {
case message:= <-messageChannel:
fmt.Println(message)
case canselMessage:= <-canselChannel:
fmt.Println(canselMessage)
break LOOP
}
}
про "конвеер" стоит упомянуть что он исполняется справа налево, из вашего примера это не очевидно
хм… это существенно снижает полезность конструкции
let result = exclaim(capitalize(doubleSay("hello")));
result //=> "Hello, hello!"
let result = "hello"
|> doubleSay
|> capitalize
|> exclaim;
result //=> "Hello, hello!"
Отсюда. Обычный код (1-ая строка) исполняется справа-налево (doubleSay расположена правее capitalize и тем более exclaim), а piped-код как раз слева-направо.
«справа налево» это не как в баше/эликсире?
Это смотря что вы понимаете под справедливостью.
Но иногда программисту приходится не сочинять алгоритм, а кодить готовый. В такой ситуации заведение лишней функции там где в оригинале ее не было не повысит читаемость кода, и понизит. Вот тут-то все эти метки порой и пригождаются.
А, вообще, это, в первую очередь, пример запутанного кода, что подтверждает аргументы против неструктурного контроля выполнения.
Качество интерпретатора соответствует качеству языка, не так ли?
с учетом RFC 4180. Это не займет много времени.Увы, коллега, не похоже на то. Я начал смотреть, что такое php_mblen и уже это заняло много времени с учётом контекста функции. Дело в том, что php_mblen в зависимости от значений макросов при сборке и переменных окружения может быть гарантировано однобайтным. Тогда неясно, на каком основании выполняется эта часть
*ptr == '\0' ? 1
потому что в таком случае цикл может остановиться только по достижении конца массива и вообще нет смысла его перебирать, так как сразу можно перейти к концу(экономия процессорного времени, да). Указанная Вами функция не является самосотоятельной частью, воплощающей стандарт, а зависит от объемлющего кода. Откуда мне знать обо всех задумках или ошибках, которые были здесь допущены. Например, почему в многобайтной кодировке, точный тип который задаётся окружением, символ перевода строки задаётся одним char? Здаётся мне, что в этой функции проблемы не исчерпываются использованием неструктурных переходов.
Ну и, наконец, неужели для того, чтобы понять, что двойное копирование строки для 1-байтных последовательностей — это нечто неоптимальное, Вам нужен другой алгоритм?
В мультибайтовых кодировках существует обратная совместимость с ASCIIИз чего это следует? Я в стандарте этого не видел. Можете указать, где это сказано точно? Если это так, то алгоритм можно написать намного эффективней, так как mblen тяжеловесна.
И всё становится предельно ясно.Тогда объясните мне, пожалуйста, как это понимать:
*ptr == '\0' ? 1
для таких настроек: #ifndef HAVE_MBLEN
# define php_mblen(ptr, len) 1
И почему при обратной совместимости в качестве конца строки используется не '\0', а null wide character.
Мы всё еще рассматриваем пользу ссылок в некоторых случаях.Можно конкретней? Ссылок на то, что
last_chars[0] = last_chars[1];
last_chars[1] = *ptr;
Приводит к двойному копированию последовательностей 1-байтовых символов, или что-то ещё?
Согласно RFC3629, например, у кодировки переменная длина символа. Можно ли в таком случае обойтись без mblen?
RFC3629 — это же UTF-8, или я что-то не понимаю? Там как раз все просто: никаких отличий от однобайтовых кодировок конкретно в этой задаче (символы CR, LF и конец строки кодируются одинаково и с частями других символов их спутать невозможно).
Собственно, UTF-8 специально же так проектировалась чтобы большинство старых строковых алгоритмов могло с ней нормально работать.
Согласно RFC3629При чём тут UTF-8? В стандарте ISO C на mblen, который используется в определении php_mblen, сказано, что он определяет количество байт символа кодировки, определяемой по LC_CTYPE. На каком основании в алгоритме делаются вещи, будто бы LC_CTYPE задаёт именно UTF-8? Повторю ещё раз, покажите, пожалуйста, место, где Вы увидели в стандарте на mblen, что
В мультибайтовых кодировках существует обратная совместимость с ASCII
Можно ли в таком случае обойтись без mblen?Если же задачей всё ограничивается именно UTF-8, то, конечно, mblen — это избыточный тормоз.
Палка на двух концах.Это объяснение? Потому что совместимо, поэтому не совместимо?
Уверены? :)Удивляюсь, что это нужно объяснять. :)
Как в таком случае можно рассуждать об оптимальности кода?Потому что для оценки не всегда нужен весь контекст, а вот для создания корректного алгоритма — нужен. Особенно, если окажется, что изначальный алгоритм некорректен и сравнение бессмысленно.
В этом-то и проблема плохого кода, что его потом и не перепишешь, не нарвавшись на остальные пахучие особенности общего кода.
if (inc_len == 0) {
break;
}
else if (inc_len > 0) {
last_chars[0] = last_chars[1];
last_chars[1] = *ptr;
ptr += inc_len;
len -= inc_len;
}
else {
php_mb_reset();
ptr++;
len--;
}
Как бы вы переписали следующий код и почему?
check : if( this.status === 'check' ) {
for( let master of this.masters ) {
master.value()
if( this.status !== 'check' ) break check
}
if( this.error ) throw this.error
return this.cache
}
this.status = 'compute'
const result = this.handler()
return this.push( result )
if(this.status === "check"){
const breakCheckValue = this.masters.find(m => {
m.value();
return this.status !== "check";
});
if( this.error )
throw this.error;
if(breakCheckValue)
return this.cache;
}
this.status = "compute";
const result = this.handler();
return this.push( result );
Почему? Ну во первых ваш пост звучал как вызов:)
Во вторых потому что без метки, а про функционал метки я узнал из этой статьи и вряд ли кто-то в моей команде ещё про этот функционал знает.
У this.masters нет find ибо это экземпляр Set. Ну и логика у вас сложная и не правильная получилась. Очевидно вы хотели воспользоваться более уместным some, а не find, но всё равно запутались в логических отрицаниях. К тому же проверка this.errors вдруг перестала зависеть от изменения this.status.
Ну а если никто в вашей команде не знает языка, на котором пишет, то у меня для вас плохие новости :-)
Напишу ещё раз:
if(this.status === "check" && allChecked(this.masters)) {
if( this.error ) throw this.error;
return this.cache;
}
this.status = "compute";
const result = this.handler();
return this.push( result );
function allChecked(masters){
return [...masters].every(m => {
m.value();
return m.status === "check";
});
}
Этот код на мой взгляд существенно чище чем ваш.
Замечательно, кода стало ещё больше и теперь нужно глазами мотать вверх-вниз чтобы понять что тут происходит. А происходит опять что-то не то, ибо вынося код в отдельную функцию вы разумно споткнулись на this.status
и "починили" это заменив на m.status
. Соответственно и функцию назвали в соответствии с тем, что она делает, а не с тем, что она должна делать.
Кроме того, выносом в отдельный метод вы увеличили размер стека процентов на 25%, ибо код этот находится в рекурсивно вызываемой функции. Тем самым вы увеличили риск получить ошибку переполнения стека, а также замусорили stack trace и flame chart, увеличив их вывод на те же 25%.
Ну и в довершении всего, у вас тут происходит лишнее выделение памяти на создание массива, чтобы проитерироваться лишь по части его элементов. Это существенно замедлило функцию, которая выполняется очень часто.
Я ставил перед собой задачу написать более читаемый код, который делает тоже самое. Оптимизация это отдельная задача выполняемая по необходимости.
Вы бы вообще над дизайном класса подумали, почему например метод .value() меняет поля чужого класса? Может ему лучше возвращать данные и плевать исключения в случае ошибки, тогда вопросов как написать красивый код будет меньше?
Мотать глазами не надо, спокойно можно читать сверху вниз. (даже после фикса проверки поля status, const self = this;… self.staus === «checked» )
Кстати конвертацию Set`а в массив наверняка js движок оптимизирует и лишняя память не будет выделена. Вызов метода наверное заинлайнится, так что стек не вырастит.
Это же вы делаете необоснованные предположения, что this.masters — это масссив, а косвенная рекурсия невозможна. С чего вы это взяли?
value
не меняет поля чужого класса, он лишь запускает пользовательские вычисления, которые могут косвенно инвалидировать кеш. Именно это тут и check
-ается. Если кеш не инвалидировался, то мы возвращаем значение из него, иначе вычисляем заново.
Прыгать глазами надо ровно до тех пор, пока вы не отразите в её названии всё, что она делает. Что по длине будет сопоставимо с её исходным кодом.
Откуда такая уверенность, что компилятор сумеет развернуть вызов замыкания для каждого элемента из массива созданного из множества в итерирование по элементам множества?
Давайте подытожим...
Какие мы заимели проблемы:
- Код стал в полтора раза больше.
- Вместо нескольких простых условий появилось одно комплексное. Третее в ту же строку добавим?
- Добавилась новая функция с названием, не соответствующим действительности.
- Замедлилось время исполнения.
- Увеличилось потребление стека.
- Усложнились отладка (точки останова, исполнение по шагам, лишние элементы в стеке, просмотр значений переменных из разных скоупов)
- Усложнился профайлинг (увеличение графика, дробление "собственного" времени, сортировка по "собственному" времени).
Чего мы добились:
- С третьей попытки героически избавились от использования стандартной, но редко необходимой конструкции языка.
Оно того стоило?
if( this.status === 'check' ) {
for( let master of this.masters ) {
master.value()
if( this.status !== 'check' ) break;
}
if( this.status === 'check' ) {
if( this.error ) throw this.error
return this.cache
}
}
// ...
На одно сравнение со строкой больше — зато воспринимается проще.
Замечательно, теперь у вас две строчки в которые изменения нужно вносить синхронно иначе бо-бо. Ок, после рефакторинга мой код стал таким:
check : if( this.status === 'check' ) {
for( let master of this.masters ) {
master.value()
if( this.status === 'obsolete' ) break check
if( this.status === 'sleep' ) break check
}
if( this.error ) throw this.error
return this.cache
}
Как вы измените свой?
А вам в целом не кажется, что этот код (и тот что выше) какой-то мутный. Столько всего понамешано, даром, что строк мало. Тут и throw, и метки, и смена статуса по вызову value()
(без знания кодовой базы вообще неочевидный момент), и какой-то cache
. Мне кажется использование тут меток не является проблемой. Проблемой является вообще существование этого. Что это? Почему оно такое странное? Причина в производительности?
Это новая реализация вот этой библиотеки, с поддержкой квантификации вычислений: https://habrahabr.ru/post/317360/
Сможете реализовать менее "мутно" — я вам спасибо скажу :-)
function computedValue() {
this.status = 'compute'
const result = this.handler()
return this.push(result)
}
function value() {
if (this.status === 'check') {
for (let master of this.masters) {
master.value()
if (this.status !== 'check') return computedValue()
}
if (this.error) throw this.error
return this.cache
}
return computedValue()
}
Какие мы заимели проблемы:
- Код стал в полтора раза больше.
- Функция разделена на 2 без практической ценности (
computedValue
опасно вызывать где-либо, кроме как вvalue
). - Увеличилось потребление стека (опять же косвенная рекурсия).
- Усложнились отладка.
- Усложнился профайлинг.
Чего мы добились:
- Избавились от использования стандартной, но редко необходимой конструкции языка.
Оно того стоило?
2. computedValue() вполне может вызываться где-то ещё, где кэширование явно не нужно. Если её опасно отдавать пользователю, можно сделать её приватной.
Кроме того, в вашем варианте одна функция занимается двумя вещами: обслуживанием вычисления значения (выставление правильного статуса перед вызовом хэндлера, сохранением результата в правильном месте) и управлением кэшированием. Это нарушение SRP. Если в каком-то другом месте нужно будет вызвать хэндлер, нужно будет опять не забыть, какой выставить статус и куда девать результат.
3. У вас рекурсия без явного ограничения глубины? Т.е. кто-то может запросто устроить бесконечную рекурсию и его спасёт только стэк оверфлоу?
А вообще, тут хвостовой вызов, который скорее всего соптимизируется (tail call optimization).
4. Отладка упростилась, потому что для каждой функции можно написать отдельный юнит-тест, и отлаживать их в принципе не нужно будет. А если кто-то будет раскручивать стек, то по имени функции увидит, какой элемент функциональности выполнялся.
5. Не вижу усложнения.
2 — value()
я не привёл так как приведённый код это далеко не всё её содержимое. В частности весь приведённый код завёрнут в try-catch для перехвата ошибок. Именно поэтому вызывать computedValue
из других мест нельзя. Да и ситуаций, когда "кэширование явно не нужно" нету, ибо сам этот класс применяется, когда нужно кеширование. Вы делаете слишком далекоидущие выводы по обрывку кода.
3 — тут нет хвостового вызова. Хэндлер может обратиться к другим реактивным переменным (и зачастую так и поступает) в которых вызывается эта же функция, но в другом контексте. Циклическая зависимость разумеется отсекается — код для этого располагается выше, но его я опять же не привёл, ибо он не имеет отношения к обсуждаемому вопросу.
4 — не путайте отладку и тесты. Отладка вступает в дело, когда тесты упали и нужно понять почему. Кроме того юнит тесты бесполезны для коммуникационного модуля. А отладка — это далеко не только "раскручивание стека", но ещё и: точки останова, исполнение по шагам, просмотр значений переменных из разных скоупов (нужно скакать по стеку, чтобы посмотреть их содержимое).
5 — ну вот если бы вы им пользовались, то заметили бы следующие проблемы: увеличение высоты графика, дробление "собственного" времени на несколько функций и улёт этих функций вниз при сортировке по собственному времени.
Я думаю речь идёт о том, что задачу под которую уместно применить метки можно и за всю жизнь не встретить в нашей области, но вот у новичка может возникнуть желание воткнуть их куда-нибудь туда, где вместо меток, надо переделать архитектуру. Чтобы не было позывов применять быстрые решения, вместо правильных.
P.S. лично я ещё не встречался с необходимостью применять метки. Но допускаю, что такие случаи бывают.
Если бы в Си не было goto, то чел, который писал функцию в интерпретаторе PHP из примера выше, напряг бы мозг и написал её нормально.
Я, может, плохой кодер, но для меня что пример, который вы переписывали, что ваш код выглядят примерно одинаково. Это даже если закрыть глаза на то, что ваша логика не соответствует примеру, который вы хотели переписать.
Если функция достаточно большая и имеет большое количество последовательно выполняемых ветвлений, без goto код получается просто монструозным, причём даже всё что можно выносить в отдельные функции.
код с goto длинный, непонятный
Ну, собственно, о чём я и говорил — никаких объективных минусов никто не называет.
потенциально глючный
А так вообще про огромное количество кода можно заявить, особенно если всякие оговорки к этому добавлять.
А в чем логика моего кода не соответствует исходному?
А в чем логика моего кода не соответствует исходному?
Вы походу чуть ошиблись со скобочками. В оригинале:
switch (inc_len) {
case -2:
case -1:
inc_len = 1;
php_mb_reset();
break;
case 0:
goto quit_loop;
case 1:
default:
last_chars[0] = last_chars[1];
last_chars[1] = *ptr;
break;
}
ptr += inc_len;
len -= inc_len;
}
ptr += inc_len;
len -= inc_len;
Находятся вне switch, т.е. будут исполнены в любом случае. В вашем же коде они исполнятся только если inc_len > 0
Для inc_len == 0 эти два выражения как-раз не будут выполнены, потому что там goto, который выводит из цикла.
Для inc_len == -2 или -1 переменная inc_len устанавливается в 1, и я заменил эти два выражения на более эффективные: ptr++ и len--. Раз, уж, в исходном комментарии шла речь об эффективности.
Насчет минусов это не лично к вам претензия. Но судя потому, что люди минуснули, они не врубились в логику исходной функции с goto. Что подтверждает то, что в этом примере goto затрудняет понимание. Т.е. вариант без goto тут и понятней, и эффективнее, и короче, и менее глючный.
В моем же варианте любые отрицательные значения (-1, -2, -100), обозначающие что в строке что-то не найдено, интерпретируются одинаково. ptr увеличивается на единицу и поиск продолжается.
Конечно, в моем варианте логика немного изменилась. Но это просто исправление ошибки наркоманов-авторов PHP.
В индусском коде с goto скорее всего будет segmentation fault. Потому что будет выполнена ветка default. Она очевидно рассчитана только на положительные значения.
Не понимаю с чего вы это взяли.
default:
last_chars[0] = last_chars[1];
last_chars[1] = *ptr;
break;
Отработает вполне корректно. Ведь если inc_len == -100, ещё не значит, что ptr отрицательный. Далее выполнится:
ptr += inc_len;
len -= inc_len;
И цикл может вполне закончится. В вашем же случае этого не произойдёт.
Да, в оригинальной функции на выходе ptr может указывать не туда куда надо, но, насколько понимаю, php_mblen не должна возвращать настолько отрицательные выражения. Так что логика у вас таки поменялась.
И я не пойму с чего вы так уверены, что ваши два эльса работают быстрее чем switch.
А len увеличится на 100! При том, что условие выхода из цикла len > 0. На следующей итерации либо php_mblen упадет с segmentation fault. Либо этот цикл будет вечным, потому что len всегда будет положительным.
Мой вариант работает быстрее потому, что
1) меньше сравнений, у меня 3 ветки (=0, >0, <0), а в варианте со switch 5 веток (-2, -1, 0, 1, default),
2) мои сравнения занимают меньше тактов процессора. Я уже 20 лет не писал на ассемблере, но на сколько я помню, сравнение с 0 или определение знака операнда быстрее, чем сравнение с -2, -1, 1.
3) в моем варианте для случая inc_len < 0 нет лишнего присваивания inc_len = 1, с последующим прибавлением и вычитанием. Вместо этого инкремент ptr++ и декремент len--. Которые так же занимают меньше тактов процессора, чем прибавление/вычитание.
Можно было бы скомпилировать этот код и проверить какие машинные инструкции генерятся, посчитать такты, но это тема для статьи, а не комментария.
Насчет изменения логики. Для inc_len >= -2 логика не изменилась. Она изменилась только для inc_len < -2 (чего как вы утверждаете быть не должно), что я сделал сознательно, чтобы исправить ошибку индусов.
Вы всё ещё настаиваете, что goto в данном случае — это хорошо? В этой теме по-моему я единственный смог понять как этот индусский код реально работает.
Вообще, я особо не старался вникнуть в код, потому что, вполне возможно есть какие-то нюансы, зависящие от окружения.
Хотя я всегда считал что switch работает быстрее if, думаю без ассемблерного кода сравнивать бессмысленно. Собственно, что касается инкремента и декремента — тут вы вероятнее всего ошибаетесь. Подобная форма записи, насколько я знаю, даёт выигрыш, только если используется предикативная форма ++ptr, в противном случае, по скорости ptr++ эквивалентно ptr += inc_len если их формат совпадает.
Однако, разговор в общем-то изначально был не об этом. Вполне возможно вы в целом правы и ваш пример лучше. Также вполне возможно, есть какие-то нюансы, которых не учли вы, изменив логику, которые могут выявиться только на тесте.
Что касается goto, я нигде не писал, что «в данном случае» это хорошо. Я лишь сказал что есть случаи, в которых без него либо будет плохо, либо вообще не сделать. Просто потому что у меня в продакшене был код, который я пробовал написать и на if и на циклах и на goto. И самый читаемый вариант получился как раз на нём.
Всем этим я лишь хочу сказать, что goto это ровно такой же инструмент как и все остальные и пользоваться им надо с умом. А если генерить бредовый код, он всё равно останется бредовым, и не важно как он написан.
он был весь на го ту.
тот ещё адок, уж поверьте.
А минуса два:
- непредсказуемые переходы (не очень понятно, куда и при каких условиях перейдет управление). Особенно забавно, если goto в середину какой-нибудь функции прыгает. Не знаю, можно ли так в JS, но и проверять не буду
- непредсказуемый контекст. Вот прыгнуло исполнение в какое-то тридцатьтретье место. Переменные и константы теперь какие значения имеют? Надо ходить, смотреть, вспоминать.
При помощи конструкций break <label>;
и continue <label>;
можно только выходить из блоков, но не входить в них. Тут все же переходы остаются предсказуемыми, хоть и получаются страшно некрасивыми.
program p1;
var a,b,k,l,d,e:integer;
c:boolean;
label l1;
begin
write('Type a, please ');
readln(a);
a:=(abs(a));
write('Type b, please ');
readln(b);
If b>0 then
begin
k:=0;
l:=0;
repeat
d:= (a mod 10);
e:= (b mod 10);
c:=(d=e);
If C then
begin
k:=(d+k);
l:=(e+l);
a:=(a div 10);
b:=(b div 10);
end
else
begin
writeln('Не входит');
goto l1;
end;
until b=0;
Writeln('Входит');
l1:
end
else writeln('Вы офигели!! Отрицательных цифр в записе числа не бывает!')
end.
Я до сих пор помню, как у меня управление скакало вниз-вверх, и это при том, что тут один-единственный goto. Да, тут плохое форматирование, плохое именование переменных и неоптимальность, окей. Но даже если бы это было, понять флоу в такой программе анрил. Я помню свои собственные ощущения. Глаза вверх-вниз вверх-вниз. И отладочная печать после каждой строчки (про watch я, конечно же, не знал).
Есть один известный дядька, он даже целый термин для этого goto придумал.
это почти никогда не надо, но знать полезно.
const a = (a, b) => (
a = a + b,
a * 2
)
for(let i = 0; i < n; i++, j++) {
}
И не только в JS.
for(; someAction(););
// просто для примера, данный код работоспособен
function someAction() {
someAction.i = someAction.i || 0;
console.log(someAction.i++);
return someAction.i < 10;
}
в for можно выполнять что угодно без тела цикла, но это ухудшает читаемость
то же самое относится к чудесам с запятой, описанным в статье — если внедрять такую практику, код станет заметно более плохим
отвечая на ваш вопрос — да, этот конкретный случай — полный аналог while
он лишь служит иллюстрацией, что в любой из трех позиций внутри for можно использовать функции — первая выполнится один раз перед запуском цикла, вторая и третья будут выполняться каждый цикл — вторая перед очередным прогоном, третья после
но если нам нужен цикл со «сменным поведением», обороты вроде
while(doBehavior());
или
for(initBehavior();doBehavior();afterDoBehavior());
открывают целые бездны гибкой настройки кода, адаптивных алгоритмов и майндфаков
Если участок кода станет "горячим" — оптимизатор разберётся, если не станет — этих накладных расходов никто не заметит. Так что в угоду читаемости (а не так как в примере выше) можно.
const a = [1,2,3,4,5];
for (let i = 0, j = a.length - 1, t; i < j; i++, j--) {
t = a[i];
a[i] = a[j];
a[j] = t;
}
console.log(a);
Правда пришлось ввести дополнительную переменную. В го выглядит красивее, хотя синтаксис там несколько другой:
a := []int{1,2,3,4,5}
for i, j := 0, len(a) - 1; i < j; i, j = i +1, j - 1 {
a[i], a[j] = a[j], a[i]
}
Из:
function toSortReverseArray(str){
str = str.split('');
str = sort();
return str.reverse();
}
В:
function toSortReverseArray(str){
return str = str.split(''), str.sort.reverse();
}
Скорее всего привел плохой пример. Советую посмотреть минифированный код некоторых скриптов, где есть примеры их правильного использования. Запятые часто помогают упростить код, но злоупотреблять не стоит
Советую посмотреть минифированный код некоторых скриптов, где есть примеры их правильного использования
Всё таки задачи у программиста и у программы-минификатора стоят разные. И довольно сложно в результате работы последней найти что-то полезное для обычного кода.
P.S. приведённый вами пример яркий пример почему запятых стоит избегать. Это вообще очень плохая мания — экономить строчки за счёт читаемости и очевидности кода.
.querySelector
, пропущу почему, использую .getElementsByTagName[tag][0]
, .getElementsByClassName[class][0]
, .getElementById(id)
и .querySelectorAll
теми же средствами, хотя это не совсем к теме, скорее к примерам.Да нет уж, вы не пропускайте и обоснуйте, почему пишете на js как в 2005 и считаете это правильным.
Если откровенно, то разница в производительности довольно сильно меня поражает. Однако я не припомню ни одного случая, где бы мне приходилось руками перебирать в цикле поиск более чем 10 элементов, например. Но на заметку возьму.
Передача аргументов в setTimeout
доступна была не всегда, а, например, с IE9. Об этом как раз написано на MDN.
В чем отличие personEl и person?let personEl = document.querySelector('#person'); console.log(person.dataset)
для элемента с #person браузер автоматически создаёт глобальную переменную person.
поэтому и проскочила person. Спасибо.
Не во всех браузерах автоматом создается person?
1. Потому что они глобальные. Если в вашем коде будет другая глобальная переменная person, то код, который работал с элементом #person, сломается.
2. Изначально не все браузеры это поддерживали. Первым был IE, остальные подтянулись для совместимости.
В общем не надо так делать. Знать об этом полезно, использовать — нет.
if (let x = Something(), x > 0) — ограничим область видимости подобно переменной для for
for (var e = document.getElementById('smth'; e; e = e.parentNode) — итерация вверх по дому
if (let x = Something(), x > 0)
?
if (Something() > 0)
Ну вот вам более конкретный код:
if (let x = foo.indexOf(bar), x > -1) {
bar.splice(x, 1);
}
Как вы здесь без переменной обойдетесь?
PS кстати, в каких версиях языка так можно делать?
кстати, в каких версиях языка так можно делать?
Это хороший вопрос, учитывая что последний хром такое отказывается исполнять. Может a-tk скажет?
Вообще, эта конструкция, конечно, выглядит как что-то вредное с точки зрения читаемости. Я б такое не стал писать… Но пример забавный. Хотел написать «жаль, не рабочий», но нет, как-то и не жаль даже.
if (let x = Something(), x > 0)
Тут проблема в том, про при присвоении (операторы
var
, let
и const
), запятая интерпретируется как перечисление переменных:let x = 1, y, z = 3;
И возможно только присвоение, иначе будет синтаксическая ошибка. Я к тому, что в принципе такой код невозможен:
let x = Something(), x > 0;
Если такое написание станет популярным — может, и в Javascript его однажды добавят.
Но вот тут пишут про shared data между воркерами — http://2ality.com/2017/01/shared-array-buffer.html#shared-array-buffers.
Возможно этому пропоузалу дали жизнь.
Автор, ты мой герой, теперь я знаю больше.
Когда я был маленький и учил C++, я очень радовался, что там можно переопределить operator,()
так, чтобы (x, y)
обозначало скалярное произведение векторов x
и y
. Потому что всякие математики его часто именно так обозначают, а не точечкой и не треугольными скобочками. Правда, скобочки тут не нужны, но из-за приоритета всё равно нужны. P.S. не делайте так :/
Предлагаю хабру запретить анимированные картинки до ката.
if(typeof myVar=='undefined')
превращается в
if(myVar== void 0)
Правда, может усложнить читабельность тем, кто не знает про void (если в документе по стилю кода этот момент есть, то, как по мне, допустимо).
А вот запятая это хороший элемент для минификаторов кода, читабельность у нее однозначно нулевая.
За доп. параметры setTimeout спасибо. Не уверен что стоит так писать с точки зрения понятность кода, но каких-то набросков вполне сгодится.
и с async както странно…
Возможности JavaScript, о существовании которых я не знал