Недавно по Твиттеру и Реддиту гулял интересный кусок кода на JavaScript. Вопрос, связанный с ним, заключался в следующем: «Может ли выражение
Сегодня мы разберём этот код и постараемся его понять.
Вот он:
Если вы используете Google Chrome, откройте консоль инструментов разработчика с помощью комбинации клавиш
На самом деле, ничего удивительного тут нет. Просто этот код использует две базовые концепции JavaScript:
Обратите внимание на то, что в исследуемом выражении,
В JavaScript имеется встроенный метод для преобразования объекта в примитивное значение:
Создадим объект:
Как сказано выше, когда мы вызываем
Кроме того, мы можем использовать
Самое интересное при работе с
Здесь мы заменили стандартный метод
Всё это ведёт к следующему:
Как видно, теперь
Теперь поговорим о том, почему это важно.
При вычислении результата операции нестрогого равенства для операндов различных типов JavaScript попытается произвести приведение типов — то есть он сделает попытку привести (конвертировать) операнды к похожим типам или к одному и тому же типу.
В нашем выражении,
Так как мы изменили стандартный метод
Неужто задача решена? Пока нет, но осталось — всего ничего.
Теперь нам нужен способ систематически увеличивать значение
Этот оператор просто добавляет значение правого операнда к переменной, которая находится слева, и присваивает этой переменной полученное значение. Вот простой пример:
Как видите, каждый раз, когда мы используем оператор присваивания со сложением, значение переменной увеличивается! Используем эту идею в нашем методе
Вместо того чтобы просто возвращать
После того, как в код внесено это изменение, мы наконец можем всё опробовать:
Работает!
Помните о том, что при использовании оператора нестрогого равенства JS пытается выполнить приведение типов. Наш объект вызывает метод
Возможно, полезно будет рассмотреть происходящее пошагово:
Полагаем, примеры, подобные разобранному выше, помогают, во-первых, лучше изучить базовые возможности JavaScript, а во-вторых — не дают забыть о том, что в JS не всё является тем, чем кажется.
Уважаемые читатели! Если вы знаете о каких-нибудь курьёзах из области JavaScript — просим ими поделиться.
(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 — просим ими поделиться.