Доброго времени суток, друзья!
Одним из самых распространенных вопросов, которые задают кандидатам на собеседованиях по JavaScript, является вопрос о способе обмена значениями переменных, типа: есть две переменные, a = 1 и b = 2; как сделать так, чтобы a = 2, а b = 1?
Иногда данный вопрос сопровождается уточнением «без создания временной переменной».
Давайте посмотрим, как это можно сделать. В этой небольшой заметке, рассчитанной, прежде всего, на начинающих разработчиков, мы разберем 4 способа, 2 из которых предполагают использование дополнительной памяти, а остальные 2 — нет.
Итак, поехали.
1. Деструктурирующее присваивание
Деструктурирующее присваивание позволяет извлекать значения элементов массива и помещать их в переменные:
let a
let b
[a, b] = [1, 2, 3]
console.log(a) // 1
console.log(b) // 2
[a, b] = [1, 2, 3] — это деструктурирующее присваивание, которое «деструктурирует» массив [1, 2, 3]. Переменной «a» присваивается значение 1, переменной «b» — значение 2, значение 3 остается невостребованным.
Казалось бы, имея в кармане эти знания, легко осуществить обмен значениями переменных. Можно, например, написать следующее:
let a = 1
let b = 2
[a, b] = [b, a] // [b, a] - временный массив
console.log(a)
console.log(b)
… и увидеть в консоли: Uncaught ReferenceError: Cannot access 'b' before initialization. Упс. Почему так произошло?
Дело в том, что «движок» JS начинает разбор кода (парсинг) с деструктуризации и «выясняет», что мы пытаемся работать с переменными до их инициализации (по крайней мере, так обстоит дело в Chrome). Поэтому необходимо заставить движок ждать — выполнять код последовательно сверху вниз. Этого можно достичь при помощи setTimeout:
let a = 1
let b = 2
setTimeout(() => {
[a, b] = [b, a]
console.log(a) // 2
console.log(b) // 1
}, 0)
Обратите внимание, что console.log также помещается внутрь setTimeout, в противном случае, мы получим в консоли 1 и 2, соответственно (т.е. сначала будет выполнен console.log, затем setTimeout с деструктуризацией).
Все работает, как ожидается. Переходим к следующей части Марлезонского балета.
2. Временная переменная
Создание временной переменной является классическим способом произвести обмен значениями переменных. Вот как это выглядит:
let a = 1
let b = 2
let t // временная переменная
t = a
a = b
b = t
console.log(a) // 2
console.log(b) // 1
В конце также было бы неплохо освободить память, занимаемую переменной «t» посредством t = null. Тут говорить особо не о чем, поэтому двигаемся дальше.
3. Сложение и вычитание
Мы вполне можем обойтись без создания временных сущностей. Обмен значениями переменных легко произвести с помощью элементарных арифметических операций:
let a = 1
let b = 2
a = a + b // 3
b = a - b // 1
a = a - b // 2
console.log(a) // 2
console.log(b) // 1
Следует отметить, что данный подход имеет некоторые ограничения. Во-первых, он позволяет работать только с целыми числами. Во-вторых, сумма первой операции (a = a + b) не должна превышать Number.MAX_SAFE_INTEGER.
4. Бинарный оператора «исключающее или» (XOR)
На закуску — самое интересное. Оператор XOR дает true в случае, когда операнды не равны. Вот таблица истинности этого оператора:
a | b | a ^ b |
0 | 0 | 1 |
1 | 1 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
Бинарный оператор XOR производит операцию XOR над каждым битом чисел (осуществляет их побитовое сравнение).
Например, так выполняется 5 ^ 7:
1 0 1 (5 в бинарной системе счисления)
1 1 1 (7)
0 1 0 (5 ^ 7 = 2 в бинарной системе)
Рассматриваемый оператор имеет две интересные особенности:
- n ^ n = 0: результатом сравнения двух одинаковых числе является 0
- n ^ 0 = n: результатом сравнения любого числа и 0 является данное число
Вот как можно использовать побитовое XOR для обмена значениями переменных:
let a = 1
let b = 2
a = a ^ b
b = a ^ b
a = a ^ b
console.log(a) // 2
console.log(b) // 1
Вот что здесь происходит:
1. a = a ^ b
2. b = a ^ b. В данном случае a заменяется на a ^ b. Получаем b = (a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a. Теперь b - это a.
3. a = a ^ b. В данном случае a заменяется на a = a ^ b, b - на a. Получаем a = (a ^ b) ^ a = b ^ (a ^ a) = b ^ 0 = b. Переменная a становится b.
У данного способа также существует ограничение — обмениваться можно только целыми числами.
Благодарю за внимание.
Счастливого кодинга!
webdevium
А ещё можно так:
PsyHaSTe
Обратите внимание, как тонко используется тот факт, что кортеж из двух элементов является функтором по второму параметру.
webdevium
Шикарный вид настоящего функционального подхода в действии.
SbWereWolf
можно для непосвящённых поподробней?
webdevium
Если на пальцах: мы использовали основы функционального программирования, чтоб поменять порядок редукции вычисления значений.
Эта строка выполняется в два этапа:
(b=a, 0)
устанавливает старое значенияa
дляb
и возвращает 0a = b + 0
устанавливает старое значениеb
дляa
Ещё можно заменить ноль на пустую строку, тогда и строки и числа можно менять местами.
ivan386
ua9msn
Этот комментарий полезнее чем вся статья.
webdevium
Или так: