(a==1 && a==2 && a==3)
вернуть true
?». Ответ на вопрос, как ни странно, был положительным.Сегодня мы разберём этот код и постараемся его понять.
Вот он:
const a = {
num: 0,
valueOf: function() {
return this.num += 1
}
};
const equality = (a==1 && a==2 && a==3);
console.log(equality); // true
Если вы используете Google Chrome, откройте консоль инструментов разработчика с помощью комбинации клавиш
Ctrl + Shift + J
в Windows, или Cmd + Opt + J
в macOS. Скопируйте этот код, вставьте в консоль и убедитесь в том, что на выходе и правда получается true
.В чём тут подвох?
На самом деле, ничего удивительного тут нет. Просто этот код использует две базовые концепции JavaScript:
- Оператор нестрогого равенства.
- Метод объекта
valueOf()
.
Оператор нестрогого равенства
Обратите внимание на то, что в исследуемом выражении,
(a==1 && a==2 && a==3)
, применяется оператор нестрогого равенства. Это означает, что в ходе вычисления значения этого выражения будет использоваться приведение типов, то есть, с помощью ==
сравнивать можно значения разных типов. Я уже много об этом писал, поэтому не буду тут вдаваться в подробности. Если вам нужно вспомнить особенности работы операторов сравнения в JS — обратитесь к этому материалу.Метод valueOf()
В JavaScript имеется встроенный метод для преобразования объекта в примитивное значение:
Object.prototype.valueOf()
. По умолчанию этот метод возвращает объект, для которого он был вызван.Создадим объект:
const a = {
num: 0
}
Как сказано выше, когда мы вызываем
valueOf()
для объекта a
, он просто возвращает сам объект:a.valueOf();
// {num: 0}
Кроме того, мы можем использовать
typeOf()
для проверки того, действительно ли valueOf()
возвращает объект:typeof a.valueOf();
// "object"
Пишем свой valueOf()
Самое интересное при работе с
valueOf()
заключается в том, что мы можем этот метод переопределить для того, чтобы конвертировать с его помощью объект в примитивное значение. Другими словами, можно использовать valueOf()
для возврата вместо объектов строк, чисел, логических значений, и так далее. Взгляните на следующий код:a.valueOf = function() {
return this.num;
}
Здесь мы заменили стандартный метод
valueOf()
для объекта a
. Теперь при вызове valueOf()
возвращается значение a.num
.Всё это ведёт к следующему:
a.valueOf();
// 0
Как видно, теперь
valueOf()
возвращает 0! Самое главное здесь то, что 0 — это то значение, которое назначено свойству объекта a.num
. Мы можем в этом удостовериться, выполнив несколько тестов:typeof a.valueOf();
// "number"
a.num == a.valueOf()
// true
Теперь поговорим о том, почему это важно.
Операция нестрогого равенства и приведение типов
При вычислении результата операции нестрогого равенства для операндов различных типов JavaScript попытается произвести приведение типов — то есть он сделает попытку привести (конвертировать) операнды к похожим типам или к одному и тому же типу.
В нашем выражении,
(a==1 && a==2 && a==3)
, JavaScript попытается привести объект a
к числовому типу перед сравнением его с числом. При выполнении операции приведения типа для объекта JavaScript, в первую очередь, попытается вызвать метод valueOf()
.Так как мы изменили стандартный метод
valueOf()
так, что теперь он возвращает значение a.num
, которое является числом, теперь мы можем сделать следующее:a == 0
// true
Неужто задача решена? Пока нет, но осталось — всего ничего.
Оператор присваивания со сложением
Теперь нам нужен способ систематически увеличивать значение
a.num
каждый раз, когда вызывается valueOf()
. К счастью, в JavaScript есть оператор присваивания со сложением, или оператор добавочного присваивания (+=
).Этот оператор просто добавляет значение правого операнда к переменной, которая находится слева, и присваивает этой переменной полученное значение. Вот простой пример:
let b = 1
console.log(b+=1); // 2
console.log(b+=1); // 3
console.log(b+=1); // 4
Как видите, каждый раз, когда мы используем оператор присваивания со сложением, значение переменной увеличивается! Используем эту идею в нашем методе
valueOf()
:a.valueOf = function() {
return this.num += 1;
}
Вместо того чтобы просто возвращать
this.num
, мы теперь, при каждом вызове valueOf()
, будем возвращать значение this.num
, увеличенное на 1 и записывать новое значение в this.num
.После того, как в код внесено это изменение, мы наконец можем всё опробовать:
const equality = (a==1 && a==2 && a==3);
console.log(equality); // true
Работает!
Пошаговый разбор
Помните о том, что при использовании оператора нестрогого равенства JS пытается выполнить приведение типов. Наш объект вызывает метод
valueOf()
, который возвращает a.num += 1
, другими словами, возвращает значение a.num
, увеличенное на единицу при каждом его вызове. Теперь остаётся лишь сравнить два числа. В нашем случае все сравнения выдадут true
. Возможно, полезно будет рассмотреть происходящее пошагово:
a == 1 ->
a.valueOf() == 1 ->
a.num += 1 == 1 ->
0 += 1 == 1 ->
1 == 1 -> true
a == 2 ->
a.valueOf() == 2 ->
a.num += 1 == 2 ->
1 += 1 == 2 ->
2 == 2 -> true
a == 3 ->
a.valueOf() == 3 ->
a.num += 1 == 3 ->
2 += 1 == 3 ->
3 == 3 -> true
Итоги
Полагаем, примеры, подобные разобранному выше, помогают, во-первых, лучше изучить базовые возможности JavaScript, а во-вторых — не дают забыть о том, что в JS не всё является тем, чем кажется.
Уважаемые читатели! Если вы знаете о каких-нибудь курьёзах из области JavaScript — просим ими поделиться.
Комментарии (92)
poxvuibr
25.01.2018 16:25+1Может ли в JavaScript конструкция (a==1 && a==2 && a==3) оказаться равной true?
Чего тут думать, в джаваскрипте любая конструкция с участием == может оказаться равной чему угодно :)
id_potassium_chloride
25.01.2018 16:56Я бы сказал, в JS почти любая конструкция может дать почти любой результат :)
PavelDymkov
26.01.2018 16:51+1Из комментария выше можно выделить два метода: с
.valueOf
и совсем отмороженный сwith
. Так вот второй работает и с===
:
with({ _a: 0, get a() { return ++this._a; } }) { console.log(a === 1 && a === 2 && a === 3); // true }
infrapro
25.01.2018 16:30+1Почему в заголовке фигурирует только 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; }
mayorovp
25.01.2018 16:59+3В С++ все еще проще:
struct { bool operator ==(int i) { return true; } } a;
devalone
25.01.2018 17:07Так по красивее :)
#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
shybovycha
26.01.2018 03:26а чем объявление
auto main() -> decltype(0)
красивее
int main()
???
devalone
26.01.2018 14:05Оно не красивее, просто в заголовочном файле стоит #define int FakeInt и препроцессор заменяет все вхождения int в том числе и int main() меняется на FakeInt main(), поэтому я сделал так, можно было ещё поменять на int32_t, например. Хотя сейчас посмотрел, можно было даже так
decltype(0) main()
Или так:
Int main()
и в заголовочном файле перед define добавить:
using Int = int;
khim
26.01.2018 14:14Да можно даже
signed main
написать! Но в любом случае отличие от «стандартной» формы должно насторожить…
devalone
25.01.2018 16:39А смысл этого примера? В нормальном коде этого всё равно делать никто не будет. Подобную хрень можно практически на любом языке написать, на C++ можно перегрузить оператор сложения, например.
Pilat
25.01.2018 16:44Всё-же нарваться на такой пример неприятно:
var a? = 1; var a = 2; var ?a = 3; if(a?==1 && a== 2 &&?a==3) { console.log("Why hello there!") }
devalone
25.01.2018 16:56Ни один разработчик(если он не троллит тех, кто после него будет смотреть этот код) не напишет такой херни. Поэтому вероятность наткнуться на такое не высока.
Pilat
25.01.2018 20:26-1Да с пол-пинка. Копипаст откуда-нибудь. Никогда не встречали в интернет-формах банков "не вводите номер копипастом заполняйте руками"? Я такое встречаю регулярно и часто так и получается — копипаст почему-то переносит неправильно.
Alexanqp
26.01.2018 08:09Объясните пожалуйста данный пример, почему так? Спасибо.
mayorovp
26.01.2018 08:25Это пример из ответа на SO: stackoverflow.com/a/48274520/4340086
Объяснение есть там же.
bro-dev
26.01.2018 09:11Это работает только за счет того как браузер отображает символы такие, норм редактор кода или даже консоль девтулз всё показывает.
nick_gabpe
25.01.2018 16:56Достаточно баянистый, но тем не менее любопытный пример:
'5'-3 // выводит 2 '5'+3 // выводит "53" '5'+-3 // выводит "5-3"
A1essandro
25.01.2018 17:06-1Мой любимый пример, когда я начинаю объяснять, почему мне не нравится JS (и вообще слабая типизация). Это, возможно, лично мое мнение, но python (как пример динамической типизации) со своей сильной типизацией, и, как следствие, невозможностью выполнения такого кода — менее подвержен ошибкам.
Keyten
26.01.2018 01:48+1И часто вы вычитаете число из строки?
Нет, ну правда. За годы, что я пишу на js, у меня сформировалась в голове ide, которая следит за всеми типами и предупреждает, если я складываю или вычитаю что-то, что может быть строкой.
А писать явное преобразование в Number / String везде, где может быть ошибка, вам вообще ничего не мешает. Как вы делаете это на других языках. Разве что тут вы это можете проигнорировать, а там — нет.A1essandro
26.01.2018 12:09(В профиле написано, «можно и нужно обращаться на ты»)
Ты привязываешься к конкретному примеру? Я в целом говорю, что это возможно (как и к массиву прибавить интовую 1, и внезапно получить строковую «1»), и считаю это неправильным. Если программист действительно хочет к строке «прибавить» число, тогда он должен явно указать, он хочет получить сумму чисел или произвести конкатенацию строк (т.е. один операнд привести к типу второго).
А по поводу часто или нет. Нет, ну серьезно, скорее всего такое нечасто встречается в отполированном коде. Но в момент, когда идет действительно очень активная работа с кодом, когда над тобой давлеет дедлайн — можно по ошибке что-то не туда скопипастить, либо просто не туда вписать. Программист — не машина, иногда он теряет концентрацию и может допустить глупую ошибку. Но JS в этом случае не помогает говоря: "парень, вот тут ты уточни, ты действительно так хотел написать?". Он просто проходит некорректный участок кода, как будто так и надо, говоря: "это абсолютно не мои проблемы, я просто к пустому массиву прибавлю число, и верну строку.".
justboris
26.01.2018 12:11И часто вы вычитаете число из строки?
Да регулярно, когда идет работа с пользовательским вводом
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 сконвертировать в число в момент присвоения. Но если бы требование одинаковых типов было вшито в стандарт языка, то это позволило бы избежать многих ошибок, когда разработчики из-за невнимательности или незнания допускают такие ошибки.
SuperPaintman
26.01.2018 17:11Ну и сами вы себе злой буратино. Зная, что в 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
.SuperPaintman
26.01.2018 17:18И нет, "
a
" — не останется строкой, т.к. "-
" тоже кастует в NumberSuperPaintman
26.01.2018 17:33(простите, опечатался, не "
a
", а конечно же "x
" станет числом тоже).
И в целом то, это проблема не JS, а вообще динамических языков, и языков с хоть чуть-чуть возможностью переопределять операторы или (и) неявно приводить типы.
# Python >>> "5" * 10 + "8" '55555555558'
Т.е. тут вопрос скорее к невнимательности. И тут TS или Flow, бы решили эту проблему (ну или попытались :) ).
SuperPaintman
26.01.2018 17:43Справедливости ради, конечно же не все динамические языки одновременно и слабо типизированы, но надеюсь вы поняли, о чем я.
mayorovp
26.01.2018 18:54Кстати, хорошо что вы Python упомянули. Дело в том, что этот язык — с сильной типизацией, а не со слабой как javascript. И все равно в нем, как видно, можно забыть о типах и насчитать чепуху.
Более того, аналогичную ерунду можно получить на любом языке с возможностью перегрузки операторов — даже на статически типизированных C++ или C# :-)A1essandro
26.01.2018 21:07При определённой сноровке можно чепуху нагородить в любом языке. Другое дело, что язык с сильной типизацией, в данном примере, выдаст ошибку в рантайме, а со статической — вообще при компиляции. Правда, стоит упомянуть, что
var a = "5"+1;
тоже корректный код в C#, но только с оператором "+" и только если один из операндов — строка. Вот так уже нельзя:
int a = "5" + 1;
Т.е. всё это дело приводится к строке (toString()). Даже так можно написать:
var a = "5"+new Object();
Но это, как я полагаю, сделано для читабельного кода при конкатенации строк с «нестроками».
justboris
26.01.2018 17:39На ССЗБ можно списать любые особенности в дизайне языка. Но хотелось бы, чтобы язык предохранял от таких граблей, и позволял сконцентрироваться на бизнес-логике продукта, а не отслеживании, где там число, а где забыли плюсик поставить.
SuperPaintman
26.01.2018 17:48Вы абсолютно правы, что язык должен в первую очередь быть инструментом, а не целью. Но в большинстве языков: Python —
input()
, C —getchar()
, Ruby —gets
, инпут — это строка.
Так и в JS, input Dom node — содержит исключительно строки.
justboris
26.01.2018 18:12Именно поэтому я бы перевел код на Typescript (рабочий пример).
Там компилятор сразу наругался что типы не совместимые, и ошибка не прошла бы дальше.
SuperPaintman
26.01.2018 18:30Тут трудно не согласиться (я об это тоже сказал чуть выше). Но мы все-же говорим в контексте JS.
Собственно, надеюсь, я правильно понял вашу позицию. TS действительно крутой :).
mayorovp
26.01.2018 18:59Тем не менее,
document.getElementById('layer1').style.opacity += 0.01
из обсуждения ниже в Typescript успешно скомпилируется и сделает ерунду. Более того, аналогичный глупый код скорее всего скомпилируется даже на C# или Java, а может даже на C++ (тут уже смотря как библиотека будет сделана).SuperPaintman
26.01.2018 20:03Да, т.к.
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
justboris
27.01.2018 14:58На это уже есть фича-реквест: https://github.com/Microsoft/TypeScript/issues/7989
Пока в сам Typescript такое ограничение не добавили, предлагается использовать TSLint-правило: https://palantir.github.io/tslint/rules/restrict-plus-operands/
TheShock
27.01.2018 00:00Хотя parseInt(n, 10) все-же более наглядно.
Мне больше всего нравитсяNumber(n)
A1essandro
25.01.2018 16:56На 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(); }
mayorovp
25.01.2018 17:01Только все-таки лучше писать
private static int a => ++_a;
alkozko
25.01.2018 18:50+2лучше вообще такое не писать никогда
A1essandro
25.01.2018 21:24alkozko, я с Вами полностью согласен. Как и с комментариемdevalone:
А смысл этого примера? В нормальном коде этого всё равно делать никто не будет. Подобную хрень можно практически на любом языке написать, на C++ можно перегрузить оператор сложения, например.
Просто хотел подчеркнуть, что тема актуальна не только для JS. И уж точно не является руководством к действию.
GraDea
25.01.2018 22:25Почему столь категорично? У нас есть что-то похожее в тестовом DSL как заменитель автоикремента в БД.
mayorovp
25.01.2018 23:20Нарушается семантика свойства. Свойство — это "эмулятор поля", предполагается что его значение не должно меняться просто от факта обращения к нему.
Вот метод вида
int a() => ++_a;
— это нормально. А подобное свойство — нет.
impwx
25.01.2018 20:59+1Есть способ гораздо проще и стабильнее:
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");
zawodskoj
25.01.2018 17:03-1#include <stdio.h> #define a (__COUNTER__+1) int main(void) { if (a == 1 && a == 2 && a == 3) printf("123123123"); return 0; }
SuperPaintman
25.01.2018 17:53Не совсем понимаю за что вас минусуют. Если вы не против, я добавлю комментарии к вашему коду, что-бы читателям стало понятно.
Для начала, это абсолютно валидный код с точки зрения
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
zawodskoj
25.01.2018 17:55А заминусовали меня скорее всего потому, что я скопипастил этот код из своего ideone, который содержал слово, которое даже матом не считается, и не успел быстро отредактировать.
Первый минус проставил тот, кто увидел, а второй кто-то «НУ ПОТОМУ ЧТО ТУТ МИНУС СТОИТ, ЗНАЧИТ ПЛОХОЙ КОММЕНТ, НАДО ТОЖЕ МИНУСНУТЬ»
Вот и пиши после этого комменты вообщеmayorovp
25.01.2018 18:44-2Если бы все было как вы написали — то и двух плюсов вы бы не получили.
Все проще. Два человека увидели версию с матом и поставили минус. Не забывайте, каждый видит ту версию комментария которая была на момент последнего обновления комментариев или загрузки страницы.
devalone
25.01.2018 17:58Забей, я как-то написал, что правильно не «Силиконовая долина», а «Кремниевая долина» и меня заминусили(самые обиженные даже в карму пошли минусовать).
fireSparrow
26.01.2018 11:52-1Мне на гиктаймсе на 10 пунктов слили кармы за то, что я вместо «последняя книга автора» написал «крайняя книга автора», а потом на комментарий с претензией ответил, что считаю этот вариант вполне допустимым и он мне больше нравится.
thedsi666
26.01.2018 01:06__COUNTER__, несмотря на поддержку многими компиляторами, не входит в стандарт языка (к сожалению).
LioneNET
25.01.2018 17:52Извините за очень нескромный вопрос, но во всех ли интерпретаторах проверка будет идти слева на право?
SuperPaintman
25.01.2018 17:57Если интерпретатор написан по-стандарту (и здравому смыслу),
&&
будут идти слева-направо. V8 / SpiderMonkey / Chakra работают именно так
gearbox
25.01.2018 22:50здравый смысл не нужен, приоритет операторов и ассоциативность. developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Operators/Operator_Precedence что интересно — в стандарте ecma не нашел ни ассоциативности ни приоритетов.
KodyWiremane
25.01.2018 18:49+1«В JS нельзя true = false, поэтому если хотите отомстить коллегам — вот пара идей..»
artur93gev
25.01.2018 22:32можно просто
b = 0;
Object.defineProperty(window, 'a', {
set: (v) => {
},
get: () => {
return b++;
}
});iShatokhin
26.01.2018 20:52Или даже так:
const a = { num: 0, [Symbol.toPrimitive]: function() { return ++this.num; } };
mirypoko
25.01.2018 22:32Я думаю так можно на любом языке, например на C#
class A { private int a = 0; public static implicit operator Int32(A o) { o.a++; return o.a; } } class Program { static void Main(string[] args) { var a = new A(); if (a == 1 && a == 2 && a == 3) { Console.WriteLine("Неожиданный поворот"); } Console.WriteLine("Hello World!"); } }
symbix
25.01.2018 22:36Зашел увидеть эту ссылку — и не нашел. Пусть тут полежит:
Предупреждение: not safe for work! Не в том смысле, а просто работа может остановиться :-)
Ernest88
26.01.2018 12:05-2Не покидает ощущение, что JavaScript был когда-то осквернен женской логикой… Ведет он себя совсем не по машинному… Еще чуть-чуть и станет выдавать ошибки наподобии «ой, всее...»
justboris
26.01.2018 12:14+1Причем здесь Javascript?
Выше в комментариях смогли реализовать такой же пример на С++ и C#. Если этого мало, вот тут еще есть варианты на Ruby, Python и Java.
Ernest88
26.01.2018 14:08-1Тема поста у нас JS. Это мое ощущение языка, как дилетанта. Конкретный пример тут не при чем. Я пробовал писать простенькие вещи в проядке ознакомления на некоторых языках и только JS пока мне показался таким туманным, чтоли, со своими типами. Очевидные вещи не хотели работать, примерно: а = 2 + 2; иф (а == 4) зен не выполнялось, так как а == 4,0000000001. Я даже на форумах знатаков спрашивал что-то элементарное, почему не работает, а мне не верили, говорили должно работать и все.
faiwer
26.01.2018 14:15а = 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)
Ernest88
26.01.2018 14:43Может, я как-то там работал с таймерами чтоли, может и перешел незаметно к дабл типу, ну это ладно, еще можно понять. Раз знатоков много, воспользовался случаем и отрыл свой старый проблемный код, я так и не понял почему он у меня не работал.
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. Как такое может быть, что это за выборочное исполнение команд? Удалось решить как-то, путем замены += на более развернутую форму. Ну что это как не женский каприз (надеюсь никого не обидеть)?faiwer
26.01.2018 14:59document.getElementById('layer1').style.opacity // === ''
Пустая строка. Неопределённое значение. Так? Складывать строки с числами не самая умная затея. Ок, допустим вы туда принудительно вбили
'1'
.
document.getElementById('layer1').style.opacity += 0.01 "0.990.01"
Сложили сладкое с горячим — получили ерунду.
JavaScript это язык со слабой динамической типизацией. Это говорит о том, что типы приводятся к друг другу в ряде случаев. В том числе и строки с числами. Отняв от строки
'1'
число0.01
вы получите0.99
. А вот добавив — ту пургу выше. Нравится это или нет, но это так. Подобным образом работают все языки со слабой динамической типизацией. Но, скажем, вPHP
для "складывания строк" (конкатенации) предусмотрен оператор.
А в JS же используется+
. Отсюда и ваши проблемы.
Я даже на форумах знатаков спрашивал что-то элементарное, почему не работает, а мне не верили, говорили должно работать и все.
Видимо такие знатаки были.
khim
26.01.2018 16:16Ну что это как не женский каприз (надеюсь никого не обидеть)?
Это слабая типизация. Вообще все языки делятся на два класса:
— Ну что ж он такой тупой? (языки с сильной типизацией)
— Ну что это за женские капризы? (языки со слабой типизацией)
На самом деле удобнее и проще работать с первым типом, но новички неизменно выбирают второй. Просто потому что это у человека с опытом реакция на всякие сообдения типа cannot concatenate 'str' and 'int' objects это «ну что ж он такой тупой», а новичка реакция «и что ж мне теперь с этим делать?»
А что «женские капризы» появляются… ну так перепишем += на более развёрнутую форму — авось пропадут. Думать и разбираться для того, чтобы что-то кое-как работающее сотворить не требуется.
Со временем людям надоедает бороться с «капризами», они понимают, что строгая типизация — это благо… и переходят на языки со статической типизацией… но на место двух поумневших приходят два новичка — и история повторяется.
Так что языки «с женской логикой» никуда не денутся пока количество программистов растёт в геометрической прогрессии…
Nikita_ab
26.01.2018 12:05-1Скрытый текстbarbanel
26.01.2018 12:33-1Заинтересовал заголовок, залез под кат чтобы понять причину.
После того как увидел функцию ValueOf вспомнил старый баян =)
старый баян<******> к вопросу о вчерашних скриптостраданиях. Только что кодер знакомый прислал, нашёл в коде программы, написанной уволенным коллегой незадолго до ухода:
<******> #define TRUE FALSE //счастливой отладки суки
* ****** такого извращённого юмора ещё не встречалScf
26.01.2018 13:43Хорошо иллюстрирует общую проблему всех динамических языков. Если совершенно корректную функцию вызвать с параметрами не тех типов, для которых она была написана, то на выходе можно получить произвольное значение произвольного типа. Которое потом будет сохранено в переменную, передано в другие функции… В итоге получается каскад ошибок, в котором концов не найдешь.
Сишникам такое неопределенное поведение и не снилось.
Big_Shark
26.01.2018 13:59Для того чтобы этого не происходило достаточно делать строгое сравнение с учетом типа.
mayorovp
26.01.2018 14:05+1Э, нет. С точки зрения сишников, это всего лишь unspecified behavior, а не undefined. И концы в этом каскаде ошибок при некоторой сноровке найти можно.
Undefined Behavior — намного хуже, поскольку с момента UB программа перестает поддаваться логике: у условных операторов может быть исполнено обе ветви или ни одной, бесконечный цикл завершается не начавшись и т.п.
ATLAS1
26.01.2018 14:22В 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!'; }
vesper-bot
stackoverflow.com/questions/48270127/can-a-1-a-2-a-3-ever-evaluate-to-true тут больше вариантов добиться такого поведения.