JavaScript30 - Day 14

By Camilla Krag Jensen naxoc |

Day 14 of #javascript30 is understanding reference vs copy on variables.

This is where so many programmers make mistakes. Not just in JavaScript, but in most programming languages. Understanding — as in really understanding this is super important.

JavaScript and PHP are almost the same when it comes to refrence vs copy, with the exception of arrays. JavaScript assigns them by reference, and PHP doesn't. Strings, numbers and booleans are copies. Everything else (I think?) is reference.

Cloning arrays

The problem is that we can overwrite the original array when we edit the array that is pointing to it. Here is an example with tapirs, because they always make the best examples:

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Make an array with not just New World tapirs.
const allTapirs = newWorldTapirTypes;
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// There is now a wrong tapir in the new world types.
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Here is how to get a copy instead of a reference: Use slice():

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Use slice with no args
const allTapirs = newWorldTapirTypes.slice();
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Use concat():

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Use slice with no args
const allTapirs = [].concat(newWorldTapirTypes);
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Or use ES6's spread

const newWorldTapirTypes = ["Baird's", 'Brazilian', 'Rabbit', 'Mountain'];

// Use slice with no args
const allTapirs = [...newWorldTapirTypes];
allTapirs.push('Malayan');

console.dir(newWorldTapirTypes);
console.dir(allTapirs);
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain' ]
// [ 'Baird\'s', 'Brazilian', 'Rabbit', 'Mountain', 'Malayan' ]

Cloning objects

Same problem:

const tapir = {
  name: 'Timmy',
  age: 4,
  type: "Baird's",
  home: "Los Angeles"
}

const tapir2 = tapir;
tapir2.home = 'Copenhagen';

console.dir(tapir);
console.dir(tapir2);
// Taprir 1 does not live in Copenhagen!
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Copenhagen' }
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Copenhagen' }

How to go about this?

Use Object.assign

const tapir = {
  name: 'Timmy',
  age: 4,
  type: "Baird's",
  home: "Los Angeles"
}

const tapir2 = Object.assign({}, tapir, { home: "Copenhagen" });
tapir2.home = 'Copenhagen';

console.dir(tapir);
console.dir(tapir2);
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Los Angeles' }
// { name: 'Timmy', age: 4, type: 'Baird\'s', home: 'Copenhagen' }

Note that that way of "cloning" is not recursive. If you need that, then first think about why you need that. There might be a better way around it. But if you really need it — then use this "poor man's clone":

const tapir = {
  name: 'Timmy',
  age: 4,
  type: "Baird's",
  home: {
    country: 'USA',
    city: 'Los Angeles'
  }
}

const tapir2 = JSON.parse(JSON.stringify(tapir));

tapir2.home = { country: "Denmark", city: "Copenhagen" };

console.dir(tapir);
console.dir(tapir2);
// { name: 'Timmy',
//  age: 4,
//  type: 'Baird\'s',
//   home: { country: 'USA', city: 'Los Angeles' } }
// { name: 'Timmy',
//  age: 4,
//  type: 'Baird\'s',
//  home: { country: 'Denmark', city: 'Copenhagen' } }