1

I'm looking to swap two objects using a swap function with just reference to them.

A typical use-case would be like this:

function swap(a, b) {
    ...
}

var a = [ { id: 1 }, { id: 2 } ]
swap(a[0], a[0])
console.log(a) // [ { id: 2 }, { id: 1 } ]

var one = { id: 1 }
var two = { id: 2 }
swap(one, two)
console.log(one) // Should be { id: 2 }
console.log(two) // Should be { id: 1 }

(Added some more use cases. swap Should not be dependent on any array functionalities.)

Is this possible? I know that javascript's functions are copy-reference source. So essentially, swap(a, b)'s a and b is just a copy of a[0] and a[1], so it seems like I can't swap them around without actually copying the fields of a into the fields of b and vice versa, which seems impractical.


edit: @timolawl pointed out ES6's destructuring, but it doesn't seem to work with this use case:

let a = [{ id: 1 }, { id: 2 }]

let one = a[0]
let two = a[1]

console.log(a[0])
console.log(a[1])

[one, two] = [two, one]

console.log(a[0])
console.log(a[1])

jsfiddle

Community
  • 1
  • 1
Keith Yong
  • 1,026
  • 1
  • 10
  • 18
  • May want to edit the last part to state that it's not the fault of the code itself but because of the parser having to guess where to ASI. A semicolon after the first `console.log(a[1])` solves the issue as the parser incorrectly assumes the code as `console.log(a[1])[one...` – timolawl Apr 30 '16 at 03:43

3 Answers3

3

With ES6, you can use destructuring:

General case:

[a, b] = [b, a];

Example:

let one = { id: 1 };
let two = { id: 2 };

document.body.insertAdjacentHTML('beforeend', '<pre>' + JSON.stringify(one, 0, 4) + '</pre>');
document.body.insertAdjacentHTML('beforeend', '<pre>' + JSON.stringify(two, 0, 4) + '</pre>');

[one, two] = [two, one];

document.body.insertAdjacentHTML('beforeend', '<pre>' + JSON.stringify(one, 0, 4) + '</pre>');
document.body.insertAdjacentHTML('beforeend', '<pre>' + JSON.stringify(two, 0, 4) + '</pre>');
timolawl
  • 5,434
  • 13
  • 29
  • Sorry, can't use array functionality. My fault for not defining the use case properly. I edited in the original question to show what other use cases it should be able to handle. Glad to learn a new ES6 function though :) – Keith Yong Apr 30 '16 at 03:02
  • @KeithYong It doesn't have to be with arrays. You can use it with just two plain variables as demonstrated above. That's just the syntax! :) – timolawl Apr 30 '16 at 03:05
  • @timolawl Is this behavior always consistent? E.g when first read, I think it may be implemented as `one = two` then `two = one`, result would be both become the original `two` – qtuan Apr 30 '16 at 03:14
  • 1
    @qtuan Yes, that swapping behavior is consistent. This is from the MDN article on swapping variables on destructuring: "Without destructuring assignment, swapping two values requires a temporary variable (or XOR swap)" – timolawl Apr 30 '16 at 03:19
  • @timolawl your answer is perfect for me, but it's running into an issue when swapping objects stored in an array. I'm trying to debug it but could you also look into it too? https://jsfiddle.net/terda12/w03oyuyg/ – Keith Yong Apr 30 '16 at 03:22
  • @KeithYong Try adding a semicolon after your second `console.log`. If I were you, I wouldn't rely on ASI. Relying on ASI means that the parser needs to spend more time parsing your code by doing the ASI. Not to mention having semicolons makes it easier to read (for most people). I know there are people who love to rely on ASI, but times like this is one where it shows that you shouldn't rely on it. I believe the problem is that the parser interprets `console.log(a[1]) \n [one...` as `console.log(a[1])[one..` – timolawl Apr 30 '16 at 03:28
2

It's possible to refactor this function to accomplish what you want - provide the array and two indices and swap based on those. For example:

function swap(array, a, b) {
    var temp = array[a]; // store a temporary copy of the variable

    // perform the swap
    array[a] = array[b];
    array[b] = temp;
}

Note that the temporary copy is necessary because data is overwritten when you set array[a] = array[b] - so you have to store the value of array[a] beforehand.

Rushy Panchal
  • 16,979
  • 16
  • 61
  • 94
  • 1
    This won't be an option for me, as I'll be doing swapping on a complex state tree for a React application. I'm trying to swap two objects in the state tree, no matter where they are located. – Keith Yong Apr 30 '16 at 02:58
  • 1
    @KeithYong that's not particularly possible because JavaScript doesn't have a notion of pointers/references. – Rushy Panchal Apr 30 '16 at 03:00
1

Swap via Deep copy

There is no simple way. Object.assign can help but any properties down the chain are only shallow copies. There is also the cludgy JSON copy. var b = JSON.parse(JSON.stringify(a)); but this does not handle functions and ignores all properties that are unassigned, and can not deal with any cyclic data (that is all to common). You can iterate the properties, and prototype chains and use Object.hasOwnProperty but again you are left with unknowns as to how deep the copy should be, how to workout inheritance, and more

Generally a copy (or swap) needs to be aware of the meaning and context of the data. Some may consider it a short fall not in Javascript favour, but then a general swap or copy funtion sort of breaks many of the rules of functional programing, data encapsulation, etc all of which have good reasoning behind them.

This and many more reasons are why there are no moves to provide a convenient deep copy.

Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • Please never stringify +parse an object to copy it. That is horribly inefficient – Downgoat Apr 30 '16 at 22:22
  • @Downgoat LOL not to worry, I can't even bring myself to return an array or object literal, must always have the return object pre assigned and in the function arguments to keep GC in its place and out of my way. When you look at what is classed as good javascript, it's just cringe worthy. Stringify + parse is right at home in among the array concat's map's and all that other inefficient madness most people do. – Blindman67 Apr 30 '16 at 22:45