Pull to refresh

Comments 95

Может ли в JavaScript конструкция (a==1 && a==2 && a==3) оказаться равной true?

Чего тут думать, в джаваскрипте любая конструкция с участием == может оказаться равной чему угодно :)

Я бы сказал, в JS почти любая конструкция может дать почти любой результат :)

Из комментария выше можно выделить два метода: с .valueOf и совсем отмороженный с with. Так вот второй работает и с ===:
with({
    _a: 0,
    get a() {
        return ++this._a;
    }
}) {
    console.log(a === 1 && a === 2 && a === 3); // true
}
Почему в заголовке фигурирует только JavaScript? Такого же поведения можно добиться и в других языках. Вопрос только какого типа 'a'?
В C++ это, например, можно сделать так
#include <iostream>

class A {
    int _a;
public:
    A():_a(0) {};
    ~A() {};
    
    bool operator ==(int i)
    {
        return ++_a == i;
    }
};

int main(int argc, const char * argv[]) {
    
    A a;
    if (a == 1 && a == 2 && a == 3) {
        std::cout << "Hello, World!\n";
    }
    return 0;
}

Так по красивее :)
#include <iostream>

#include "happy_debugging_lol.h"

auto main() -> decltype(0)
{
    int a = 1;

    if (a == 1 && a == 2 && a == 3)
        std::cout << "WTF? Why am I seeing this?" << std::endl;
}

Не верите, что это работает? Вот содержимое файла happy_debugging_lol.h
#pragma once

class FakeInt {
public:
    FakeInt(int value)
        : value(value)
    {
    }
    operator int() const
    {
        return value;
    }
    template <typename T>
    bool operator==(const T& other) const
    {
        return true;
    }

private:
    int value;
};
#define int FakeInt

operator int() const

— вроде лишнее? Нет же в коде приведения к типу int.
Ну это я так на всякий случай написал, чтоб он был более похож на int по интерфейсу, конечно ещё надо реализовать операторы сложения, умножения и т.д.

а чем объявление


auto main() -> decltype(0)

красивее


int main()

???

Тем, что int переопределен, а decltype(0) — нет :-)
Оно не красивее, просто в заголовочном файле стоит #define int FakeInt и препроцессор заменяет все вхождения int в том числе и int main() меняется на FakeInt main(), поэтому я сделал так, можно было ещё поменять на int32_t, например. Хотя сейчас посмотрел, можно было даже так
decltype(0) main()

Или так:
Int main()

и в заголовочном файле перед define добавить:
using Int = int;
Да можно даже signed main написать! Но в любом случае отличие от «стандартной» формы должно насторожить…
А смысл этого примера? В нормальном коде этого всё равно делать никто не будет. Подобную хрень можно практически на любом языке написать, на C++ можно перегрузить оператор сложения, например.

Всё-же нарваться на такой пример неприятно:


var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
    console.log("Why hello there!")
}
Ни один разработчик(если он не троллит тех, кто после него будет смотреть этот код) не напишет такой херни. Поэтому вероятность наткнуться на такое не высока.

Да с пол-пинка. Копипаст откуда-нибудь. Никогда не встречали в интернет-формах банков "не вводите номер копипастом заполняйте руками"? Я такое встречаю регулярно и часто так и получается — копипаст почему-то переносит неправильно.

Объясните пожалуйста данный пример, почему так? Спасибо.

там не пробелы а символы Unicode похожие на пробелы.

Это работает только за счет того как браузер отображает символы такие, норм редактор кода или даже консоль девтулз всё показывает.
На всех собеседованиях этой страны :)
Достаточно баянистый, но тем не менее любопытный пример:
'5'-3        // выводит 2
'5'+3       // выводит "53"     
'5'+-3      // выводит "5-3"

Мой любимый пример, когда я начинаю объяснять, почему мне не нравится JS (и вообще слабая типизация). Это, возможно, лично мое мнение, но python (как пример динамической типизации) со своей сильной типизацией, и, как следствие, невозможностью выполнения такого кода — менее подвержен ошибкам.

В последнее время даже не пытаюсь объяснить, просто говорю js sucks

И часто вы вычитаете число из строки?
Нет, ну правда. За годы, что я пишу на js, у меня сформировалась в голове ide, которая следит за всеми типами и предупреждает, если я складываю или вычитаю что-то, что может быть строкой.
А писать явное преобразование в Number / String везде, где может быть ошибка, вам вообще ничего не мешает. Как вы делаете это на других языках. Разве что тут вы это можете проигнорировать, а там — нет.
(В профиле написано, «можно и нужно обращаться на ты»)
Ты привязываешься к конкретному примеру? Я в целом говорю, что это возможно (как и к массиву прибавить интовую 1, и внезапно получить строковую «1»), и считаю это неправильным. Если программист действительно хочет к строке «прибавить» число, тогда он должен явно указать, он хочет получить сумму чисел или произвести конкатенацию строк (т.е. один операнд привести к типу второго).

А по поводу часто или нет. Нет, ну серьезно, скорее всего такое нечасто встречается в отполированном коде. Но в момент, когда идет действительно очень активная работа с кодом, когда над тобой давлеет дедлайн — можно по ошибке что-то не туда скопипастить, либо просто не туда вписать. Программист — не машина, иногда он теряет концентрацию и может допустить глупую ошибку. Но JS в этом случае не помогает говоря: "парень, вот тут ты уточни, ты действительно так хотел написать?". Он просто проходит некорректный участок кода, как будто так и надо, говоря: "это абсолютно не мои проблемы, я просто к пустому массиву прибавлю число, и верну строку.".
И часто вы вычитаете число из строки?

Да регулярно, когда идет работа с пользовательским вводом


const a = 0.1;
const x = document.querySelector('input[name="x"]').value;
const y = document.querySelector('input[name="y"]').value;

console.log(x - a * y)

a*y скастуется в число, а x останется строкой.


По-хорошему, нужно x и y сконвертировать в число в момент присвоения. Но если бы требование одинаковых типов было вшито в стандарт языка, то это позволило бы избежать многих ошибок, когда разработчики из-за невнимательности или незнания допускают такие ошибки.

Ну и сами вы себе злой буратино. Зная, что в JS такое поведение, и что input value — это всегда строка, кто мешает вам, опять же зная, что вы будете производить мат. операции над этим инпутом, самому сконвертировать.


const a = 0.1;
const x = +document.querySelector('input[name="x"]').value; // Вот теперь это число
const y = +document.querySelector('input[name="y"]').value; // И это тоже

console.log(x - a * y)

Хотя parseInt(n, 10) все-же более наглядно и конвертирует исключительно в десятичное число (т.к. +'0xFF' === 255).


По-хорошему, нужно x и y сконвертировать в число в момент присвоения

С чего бы это? Инпут хранит исключительно значение как строку, а если вы про input[type="number"] — то это не имеет никакого отношения к JS, это лишь валидация пользовательского инпута, как и type="email", а в JS это приходит как обычная DOMString.

(простите, опечатался, не "a", а конечно же "x" станет числом тоже).


И в целом то, это проблема не JS, а вообще динамических языков, и языков с хоть чуть-чуть возможностью переопределять операторы или (и) неявно приводить типы.


# Python
>>> "5" * 10 + "8"
'55555555558'

Т.е. тут вопрос скорее к невнимательности. И тут TS или Flow, бы решили эту проблему (ну или попытались :) ).

Справедливости ради, конечно же не все динамические языки одновременно и слабо типизированы, но надеюсь вы поняли, о чем я.

Кстати, хорошо что вы Python упомянули. Дело в том, что этот язык — с сильной типизацией, а не со слабой как javascript. И все равно в нем, как видно, можно забыть о типах и насчитать чепуху.

Более того, аналогичную ерунду можно получить на любом языке с возможностью перегрузки операторов — даже на статически типизированных C++ или C# :-)
При определённой сноровке можно чепуху нагородить в любом языке. Другое дело, что язык с сильной типизацией, в данном примере, выдаст ошибку в рантайме, а со статической — вообще при компиляции. Правда, стоит упомянуть, что
var a = "5"+1;

тоже корректный код в C#, но только с оператором "+" и только если один из операндов — строка. Вот так уже нельзя:
int a = "5" + 1;

Т.е. всё это дело приводится к строке (toString()). Даже так можно написать:
var a = "5"+new Object();

Но это, как я полагаю, сделано для читабельного кода при конкатенации строк с «нестроками».

На ССЗБ можно списать любые особенности в дизайне языка. Но хотелось бы, чтобы язык предохранял от таких граблей, и позволял сконцентрироваться на бизнес-логике продукта, а не отслеживании, где там число, а где забыли плюсик поставить.

Вы абсолютно правы, что язык должен в первую очередь быть инструментом, а не целью. Но в большинстве языков: Python — input(), C — getchar(), Ruby — gets, инпут — это строка.


Так и в JS, input Dom node — содержит исключительно строки.

Именно поэтому я бы перевел код на Typescript (рабочий пример).


Там компилятор сразу наругался что типы не совместимые, и ошибка не прошла бы дальше.

Тут трудно не согласиться (я об это тоже сказал чуть выше). Но мы все-же говорим в контексте JS.


Собственно, надеюсь, я правильно понял вашу позицию. TS действительно крутой :).

Тем не менее, document.getElementById('layer1').style.opacity += 0.01 из обсуждения ниже в Typescript успешно скомпилируется и сделает ерунду. Более того, аналогичный глупый код скорее всего скомпилируется даже на C# или Java, а может даже на C++ (тут уже смотря как библиотека будет сделана).

Да, т.к. document.getElementById('layer1').style.opacity — это строка, а не число.


И формально, это равносильно этому:


let a = '0.2';

a += 0.01; // Тут Number преобразуется в строку

console.log(a); // => 0.20.01

Но тут уже важно понимание дела, т.е. TS не знает, хотите ли вы добавить символы в строку, или сложить числа.


И Flow сделает то-же самое: https://flow.org/try/#0DYUwLgBAhhC8EHICMCDcAodMDU8BMG6AxgPYB2AziaAHTAkDmAFFAJSoQD0ncAfBEjxA

О чем я и говорю. Статическая типизация — вещь хорошая, но не панацея.
Согласен. Правда в Python есть свои странности :)
На C# свойства тоже позволяют так делать:

        private static int _a = 0;
        private static int a { get => ++_a; }

        static void Main(string[] args)
        {
            if (a == 1 && a == 2 && a == 3)
            {
                Console.WriteLine("True");
            }
            Console.Read();
        }

Только все-таки лучше писать private static int a => ++_a;

лучше вообще такое не писать никогда
alkozko, я с Вами полностью согласен. Как и с комментариемdevalone:
А смысл этого примера? В нормальном коде этого всё равно делать никто не будет. Подобную хрень можно практически на любом языке написать, на C++ можно перегрузить оператор сложения, например.

Просто хотел подчеркнуть, что тема актуальна не только для JS. И уж точно не является руководством к действию.
Почему столь категорично? У нас есть что-то похожее в тестовом DSL как заменитель автоикремента в БД.

Нарушается семантика свойства. Свойство — это "эмулятор поля", предполагается что его значение не должно меняться просто от факта обращения к нему.


Вот метод вида int a() => ++_a; — это нормально. А подобное свойство — нет.

Есть способ гораздо проще и стабильнее:

class MyClass
{
    public static bool operator ==(MyClass left, int right) => true;
    public static bool operator !=(MyClass left, int right) => true;
}

var a = new MyClass();
if(a == 1 && a == 2 && a == 3)
    Console.WriteLine("Equals");

Не совсем понимаю за что вас минусуют. Если вы не против, я добавлю комментарии к вашему коду, что-бы читателям стало понятно.


Для начала, это абсолютно валидный код с точки зрения C.


#define директива — это по-сути замена во время компиляции найденого токена (или вызова) на его значени. В случае выше, любая найденная a будет заменена на (__COUNTER__+1)


__COUNTER__ — это "магическое" макро значение, как и __FILE__, __LINE__, или __FUNCTION_. И в C / C++ эти значения опять же заменяются на этапе компиляции (см. выше). Сам же __COUNTER__ "магичен" из-за того, что при каждом новом вызове, он будет инкриментировать свое значение (от 0).
В итоге выражение (__COUNTER__+1) станет (0+1), затем (1+1), затем (2+1).


И весь код будет выглядеть так:


// Т.к. `include` тоже макрос, тут будет содержимое этого файла.

int main(void) {
    if ((0+1) == 1 && (1+1) == 2 && (2+1) == 3) printf("123123123");
    return 0;
}

А дальше все еще круче, если кому интересно, компилятор просто схлопнет (оптимизирует) константные выражения, и на выходе останется:


// Т.к. `include` тоже макрос, тут будет содержимое этого файла.

int main(void) {
    printf("123123123");
    return 0;
}

Так-что не спешите кидаться на zawodskoj

А заминусовали меня скорее всего потому, что я скопипастил этот код из своего ideone, который содержал слово, которое даже матом не считается, и не успел быстро отредактировать.
Первый минус проставил тот, кто увидел, а второй кто-то «НУ ПОТОМУ ЧТО ТУТ МИНУС СТОИТ, ЗНАЧИТ ПЛОХОЙ КОММЕНТ, НАДО ТОЖЕ МИНУСНУТЬ»
Вот и пиши после этого комменты вообще
Если бы все было как вы написали — то и двух плюсов вы бы не получили.

Все проще. Два человека увидели версию с матом и поставили минус. Не забывайте, каждый видит ту версию комментария которая была на момент последнего обновления комментариев или загрузки страницы.
Забей, я как-то написал, что правильно не «Силиконовая долина», а «Кремниевая долина» и меня заминусили(самые обиженные даже в карму пошли минусовать).
Мне на гиктаймсе на 10 пунктов слили кармы за то, что я вместо «последняя книга автора» написал «крайняя книга автора», а потом на комментарий с претензией ответил, что считаю этот вариант вполне допустимым и он мне больше нравится.
Это какая-то местная фишка, минусовать)) Плюсовать в карму никто не идет почему-то ))
А нельзя плюсовать выше +4 если нет публикаций.
Я ему минус за мат ставил, сейчас мата в комментарии уже нет.
__COUNTER__, несмотря на поддержку многими компиляторами, не входит в стандарт языка (к сожалению).

Можно даже сразу:


#include <stdio.h>

#define if(cond)

int main(void) {
    if (a == 1 && a == 2 && a == 3)
        printf("Yes");
    return 0;
}
Извините за очень нескромный вопрос, но во всех ли интерпретаторах проверка будет идти слева на право?

Если интерпретатор написан по-стандарту (и здравому смыслу), && будут идти слева-направо. V8 / SpiderMonkey / Chakra работают именно так

«В JS нельзя true = false, поэтому если хотите отомстить коллегам — вот пара идей..»
можно просто
b = 0;
Object.defineProperty(window, 'a', {
set: (v) => {

},
get: () => {
return b++;
}
});
Или даже так:

const a = {
  num: 0,
  [Symbol.toPrimitive]: function() {
    return ++this.num;
  }
};
UFO just landed and posted this here

Зашел увидеть эту ссылку — и не нашел. Пусть тут полежит:


Return true to win


Предупреждение: not safe for work! Не в том смысле, а просто работа может остановиться :-)

Ну и php, куда же без него.

$a = true;
if ($a == 1 && $a = 2 && $a = 3 ) {
    echo 'I am here';
}
У вас в условии два присваивания. Но да, если бы вместо них были сравнения, выражение по-прежнему было бы истинно.
Не покидает ощущение, что JavaScript был когда-то осквернен женской логикой… Ведет он себя совсем не по машинному… Еще чуть-чуть и станет выдавать ошибки наподобии «ой, всее...»

Причем здесь Javascript?


Выше в комментариях смогли реализовать такой же пример на С++ и C#. Если этого мало, вот тут еще есть варианты на Ruby, Python и Java.

Тема поста у нас JS. Это мое ощущение языка, как дилетанта. Конкретный пример тут не при чем. Я пробовал писать простенькие вещи в проядке ознакомления на некоторых языках и только JS пока мне показался таким туманным, чтоли, со своими типами. Очевидные вещи не хотели работать, примерно: а = 2 + 2; иф (а == 4) зен не выполнялось, так как а == 4,0000000001. Я даже на форумах знатаков спрашивал что-то элементарное, почему не работает, а мне не верили, говорили должно работать и все.
а = 2 + 2; иф (а == 4) зен не выполнялось, так как а == 4,0000000001

А вам это не приснилось? В JS обыкновенные double числа. Никакой магии. Пока вы работаете с целой частью — проблем нет. Начинаете работать с дробной — привет степени двойки и прочие радости double. В принципе, всё как и везде.


According to the ECMAScript standard, there is only one number type: the double-precision 64-bit binary format IEEE 754 value (numbers between -(253 -1) and 253 -1)
Может, я как-то там работал с таймерами чтоли, может и перешел незаметно к дабл типу, ну это ладно, еще можно понять. Раз знатоков много, воспользовался случаем и отрыл свой старый проблемный код, я так и не понял почему он у меня не работал.

var layer_int;
function layer_anim() {
document.getElementById('layer1').style.opacity -= 0.01;
document.getElementById('layer2').style.opacity += 0.01;
layer_int = setTimeout('layer_anim()', 20);
}

Работает он так, что первый слой пропадает, значение падает с 1, а второй слой появляется, с нуля лишь один раз, и остается на уровне 0.01. Как такое может быть, что это за выборочное исполнение команд? Удалось решить как-то, путем замены += на более развернутую форму. Ну что это как не женский каприз (надеюсь никого не обидеть)?
document.getElementById('layer1').style.opacity
// === ''

Пустая строка. Неопределённое значение. Так? Складывать строки с числами не самая умная затея. Ок, допустим вы туда принудительно вбили '1'.


document.getElementById('layer1').style.opacity += 0.01
"0.990.01"

Сложили сладкое с горячим — получили ерунду.


JavaScript это язык со слабой динамической типизацией. Это говорит о том, что типы приводятся к друг другу в ряде случаев. В том числе и строки с числами. Отняв от строки '1' число 0.01 вы получите 0.99. А вот добавив — ту пургу выше. Нравится это или нет, но это так. Подобным образом работают все языки со слабой динамической типизацией. Но, скажем, в PHP для "складывания строк" (конкатенации) предусмотрен оператор .
А в JS же используется +. Отсюда и ваши проблемы.


Я даже на форумах знатаков спрашивал что-то элементарное, почему не работает, а мне не верили, говорили должно работать и все.

Видимо такие знатаки были.

Благодарю ) Поумнел. Видимо тогда казалось очевидным, что раз от 0 до 1, то должно быть число.

Все CSS-поля строки. А style в DOM API это проекция на язык CSS из JS-окружения.

Поставил бы вам «лайк» если бы мог ) Спасибо

Дык style.opacity — это ж строка...

Ну что это как не женский каприз (надеюсь никого не обидеть)?
Это слабая типизация. Вообще все языки делятся на два класса:
— Ну что ж он такой тупой? (языки с сильной типизацией)
— Ну что это за женские капризы? (языки со слабой типизацией)
На самом деле удобнее и проще работать с первым типом, но новички неизменно выбирают второй. Просто потому что это у человека с опытом реакция на всякие сообдения типа cannot concatenate 'str' and 'int' objects это «ну что ж он такой тупой», а новичка реакция «и что ж мне теперь с этим делать?»

А что «женские капризы» появляются… ну так перепишем += на более развёрнутую форму — авось пропадут. Думать и разбираться для того, чтобы что-то кое-как работающее сотворить не требуется.

Со временем людям надоедает бороться с «капризами», они понимают, что строгая типизация — это благо… и переходят на языки со статической типизацией… но на место двух поумневших приходят два новичка — и история повторяется.

Так что языки «с женской логикой» никуда не денутся пока количество программистов растёт в геометрической прогрессии…
Заинтересовал заголовок, залез под кат чтобы понять причину.
После того как увидел функцию ValueOf вспомнил старый баян =)
старый баян
<******> к вопросу о вчерашних скриптостраданиях. Только что кодер знакомый прислал, нашёл в коде программы, написанной уволенным коллегой незадолго до ухода:
<******> #define TRUE FALSE //счастливой отладки суки
* ****** такого извращённого юмора ещё не встречал

Хорошо иллюстрирует общую проблему всех динамических языков. Если совершенно корректную функцию вызвать с параметрами не тех типов, для которых она была написана, то на выходе можно получить произвольное значение произвольного типа. Которое потом будет сохранено в переменную, передано в другие функции… В итоге получается каскад ошибок, в котором концов не найдешь.


Сишникам такое неопределенное поведение и не снилось.

Для того чтобы этого не происходило достаточно делать строгое сравнение с учетом типа.

Э, нет. С точки зрения сишников, это всего лишь unspecified behavior, а не undefined. И концы в этом каскаде ошибок при некоторой сноровке найти можно.

Undefined Behavior — намного хуже, поскольку с момента UB программа перестает поддаваться логике: у условных операторов может быть исполнено обе ветви или ни одной, бесконечный цикл завершается не начавшись и т.п.
В PHP тоже можно:

class A {
    private $val = 0;

    public function __toString()
    {
        $val = $this->val++;
        return (string) $val;
    }
}

$a = new A();

if ($a == '0' && $a == '1' && $a == '2') {
    echo 'Its true!';
}

Имхо проще $a присвоить функцию, и при каждом вызове функция будет возвращать числа. И не надо в таком случае закатывать 1 в кавычки.

Не совсем понял. Как вызывать функцию без ()? И как в функции сохранять предыдущее значение?
Можно ещё и так ногу отстрелить:

int a;

bool func1(int b) {
a++;

return b == b;
}

a = NO_ERROR;

if ((func(a) && a==NO_ERROR))
{
#code never executed…
}

А всё почему? А потому что вызов функций который приводят к смене значений переменных внутри if-ов прямой путь к ошибкам.
Так же такое приводит к различным весёлым ошибкам при создании/сериализации в ООП
Sign up to leave a comment.