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
}
#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;
}
В С++ все еще проще:
struct {
bool operator ==(int i)
{
return true;
}
} a;
#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.
а чем объявление
auto main() -> decltype(0)
красивее
int main()
???
decltype(0) main()
Или так:
Int main()
и в заголовочном файле перед define добавить:
using Int = int;
Всё-же нарваться на такой пример неприятно:
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
console.log("Why hello there!")
}
'5'-3 // выводит 2
'5'+3 // выводит "53"
'5'+-3 // выводит "5-3"
В последнее время даже не пытаюсь объяснить, просто говорю 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
" — не останется строкой, т.к. "-
" тоже кастует в Number
(простите, опечатался, не "a
", а конечно же "x
" станет числом тоже).
И в целом то, это проблема не JS, а вообще динамических языков, и языков с хоть чуть-чуть возможностью переопределять операторы или (и) неявно приводить типы.
# Python
>>> "5" * 10 + "8"
'55555555558'
Т.е. тут вопрос скорее к невнимательности. И тут TS или Flow, бы решили эту проблему (ну или попытались :) ).
Справедливости ради, конечно же не все динамические языки одновременно и слабо типизированы, но надеюсь вы поняли, о чем я.
Более того, аналогичную ерунду можно получить на любом языке с возможностью перегрузки операторов — даже на статически типизированных 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
На это уже есть фича-реквест: https://github.com/Microsoft/TypeScript/issues/7989
Пока в сам Typescript такое ограничение не добавили, предлагается использовать TSLint-правило: https://palantir.github.io/tslint/rules/restrict-plus-operands/
Хотя parseInt(n, 10) все-же более наглядно.
Мне больше всего нравится
Number(n)
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;
А смысл этого примера? В нормальном коде этого всё равно делать никто не будет. Подобную хрень можно практически на любом языке написать, на C++ можно перегрузить оператор сложения, например.
Просто хотел подчеркнуть, что тема актуальна не только для JS. И уж точно не является руководством к действию.
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");
#include <stdio.h>
#define a (__COUNTER__+1)
int main(void) {
if (a == 1 && a == 2 && a == 3) printf("123123123");
return 0;
}
Не совсем понимаю за что вас минусуют. Если вы не против, я добавлю комментарии к вашему коду, что-бы читателям стало понятно.
Для начала, это абсолютно валидный код с точки зрения 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
Первый минус проставил тот, кто увидел, а второй кто-то «НУ ПОТОМУ ЧТО ТУТ МИНУС СТОИТ, ЗНАЧИТ ПЛОХОЙ КОММЕНТ, НАДО ТОЖЕ МИНУСНУТЬ»
Вот и пиши после этого комменты вообще
Можно даже сразу:
#include <stdio.h>
#define if(cond)
int main(void) {
if (a == 1 && a == 2 && a == 3)
printf("Yes");
return 0;
}
Если интерпретатор написан по-стандарту (и здравому смыслу), &&
будут идти слева-направо. V8 / SpiderMonkey / Chakra работают именно так
b = 0;
Object.defineProperty(window, 'a', {
set: (v) => {
},
get: () => {
return b++;
}
});
Зашел увидеть эту ссылку — и не нашел. Пусть тут полежит:
Предупреждение: not safe for work! Не в том смысле, а просто работа может остановиться :-)
$a = true;
if ($a == 1 && $a = 2 && $a = 3 ) {
echo 'I am here';
}
а = 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 же используется +
. Отсюда и ваши проблемы.
Я даже на форумах знатаков спрашивал что-то элементарное, почему не работает, а мне не верили, говорили должно работать и все.
Видимо такие знатаки были.
Дык style.opacity
— это ж строка...
Ну что это как не женский каприз (надеюсь никого не обидеть)?Это слабая типизация. Вообще все языки делятся на два класса:
— Ну что ж он такой тупой? (языки с сильной типизацией)
— Ну что это за женские капризы? (языки со слабой типизацией)
На самом деле удобнее и проще работать с первым типом, но новички неизменно выбирают второй. Просто потому что это у человека с опытом реакция на всякие сообдения типа cannot concatenate 'str' and 'int' objects это «ну что ж он такой тупой», а новичка реакция «и что ж мне теперь с этим делать?»
А что «женские капризы» появляются… ну так перепишем += на более развёрнутую форму — авось пропадут. Думать и разбираться для того, чтобы что-то кое-как работающее сотворить не требуется.
Со временем людям надоедает бороться с «капризами», они понимают, что строгая типизация — это благо… и переходят на языки со статической типизацией… но на место двух поумневших приходят два новичка — и история повторяется.
Так что языки «с женской логикой» никуда не денутся пока количество программистов растёт в геометрической прогрессии…
После того как увидел функцию ValueOf вспомнил старый баян =)
<******> #define TRUE FALSE //счастливой отладки суки
* ****** такого извращённого юмора ещё не встречал
Хорошо иллюстрирует общую проблему всех динамических языков. Если совершенно корректную функцию вызвать с параметрами не тех типов, для которых она была написана, то на выходе можно получить произвольное значение произвольного типа. Которое потом будет сохранено в переменную, передано в другие функции… В итоге получается каскад ошибок, в котором концов не найдешь.
Сишникам такое неопределенное поведение и не снилось.
Для того чтобы этого не происходило достаточно делать строгое сравнение с учетом типа.
Undefined Behavior — намного хуже, поскольку с момента UB программа перестает поддаваться логике: у условных операторов может быть исполнено обе ветви или ни одной, бесконечный цикл завершается не начавшись и т.п.
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!';
}
int a;
bool func1(int b) {
a++;
…
return b == b;
}
…
a = NO_ERROR;
…
if ((func(a) && a==NO_ERROR))
{
#code never executed…
}
А всё почему? А потому что вызов функций который приводят к смене значений переменных внутри if-ов прямой путь к ошибкам.
Так же такое приводит к различным весёлым ошибкам при создании/сериализации в ООП
Может ли в JavaScript конструкция (a==1 && a==2 && a==3) оказаться равной true?