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

Одним из самых распространенных вопросов, которые задают кандидатам на собеседованиях по 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.

У данного способа также существует ограничение — обмениваться можно только целыми числами.

Благодарю за внимание.

Счастливого кодинга!