Value vs Reference Types In Javascript

Value vs Reference Types In Javascript

Javascript differentiates Data Types on :

  • Primitive Types (Number, String, Boolean, null & undefined).
  • Complex Types (Objeccts & Arrays).

Copying Primitive Values :

When copying primitive values, Javascript is going to behave as we expect it to. We just need to see what was the value of the variable at the time of the assignment.

Copying Numbers:

let x = 10;
let y = x;

x = 2;

console.log(x);  // 2
console.log(y);  // 1

Copying Strings :

let name = "John";
let copiedName = name;

name = "Peter";

console.log(name); // John
console.log(copiedName); // Peter
  • As you can see the value of y & copiedName doesn't changed / updated, because they were assigned before updating the respective original values.
  • It means x was assigned to y when it's value was 1 & name was assigned to copiedName when it's value was John.
  • Since, Primitive Types / Values are referred to as Pass-By-Value, they just remember / store the value that was assigned to them at the time of initialization, & later if the original value(x, name) gets updated, this doesn't affect the new values(y, copiedName) because they are not subscribed to original values`.

Copying Complex Values :

When copying complex values, Javascript engine is not going to behave as you initially think it would.

Copying Arrays :

const games = [ 'cricket', 'football' ];
const copiedGames= games;

games.push('tennis');

console.log(games); // [ 'cricket', 'football', 'tennis' ]
console.log(copiedGames); // [ 'cricket', 'football', 'tennis' ]
  • As you can see the games & copiedGames returns the same array, but in the case of primitive values we got the different output.
  • Lets try with Objects.

Copying Objects :

const person = {
      firstName : "John",
      lastName : "Cena"
}
const newPerson = person;

// Change the firstName
person.firstName("Jane");

console.log(preson);  // {  firstName : "Jane",  lastName : "Cena" }
console.log(newPerson);  // {  firstName : "Jane",  lastName : "Cena" }
  • Again, we got the same object values in-case of copmlex types.
  • So, lets see what's happenning...
  • When a variable is assigned a primitive value, it just copies that value. We saw that with number and strings examples.

  • On the other hand, when a variable is assigned a non-primitive value (such as an object, an array or a function), it is given a reference to that object’s location in memory. What does that mean?

In this example above, the variable newPerson doesn’t actually contain the value { firstName: 'John', lastName: 'Cena' }, instead it points to a location in memory where that value is stored.

const person = {
      firstName : "John",
      lastName : "Cena"
}; // memory-location :- bca154
const newPerson = person; // memory-location :- bca154
  • Since, the Complex Types / Values are referred to as Pass-By-Reference, When a reference type value is copied to another variable, like newPerson in the example above, the object is copied by reference instead of value. In simple terms, person & newPerson don’t have their own copy of the value. They point to the same location in memory, for ex: memory-location :- bca154.
// Change the firstName
person.firstName = "Jane";

console.log(preson);  // {  firstName : "Jane",  lastName : "Cena" }
console.log(newPerson);  // {  firstName : "Jane",  lastName : "Cena" }
  • When a new item is pushed to person, the array in memory is modified, and as a result the variable newPerson also reflects that change.

  • We're never actually making a copy of a person object. We're just make a variable that points to the same location in the memory.

Equality

  • Since the two variables are returning the same value, lets check by creating two variables with same values & check their equality.
const personOne = {name : "John"}; //  memory-location :- bca112
const personTwo = {name: "John"};

console.log(personOne === personTwo); //  memory-location :- bca155
  • Here, both the variables personOne & personTwo have the same values, lets check what we get!
console.log(personOne === personTwo); // false
// since  bca112 !== bca155
  • You might thought that equality of these variables personOne & personTwo logs true, but that isn't true. The reason behind that is that although person & otherPerson contain identical objects, they still point to two distinct objects stored in different locations in memory.
  • Now, let's create a copy of the person object by copying the object itself, rather than creating a completely new instance of it.
const anotherPerson = personOne;

console.log(anotherPerson === personOne); // true
  • personOne & anotherPerson hold reference to the same location in memory & are therefore considered to be equal.

  • Awesome! We just learned that primitive values are copied by value, and that objects are copied by reference.

  • But, how to make a real copy of an object & remove it's reference. That will allow us to copy an object and change it without being afraid that we'll change both objects at the same time.

Shallow Cloning :

Cloning Arrays :

  • There are 2 ways to shallow-clone / copy an array.
  1. Spread Operator
  2. array.slice()

Spread Operator

  • Lets create an array of numbers & copy the original array without it's reference.
const numbers = [ 1, 2, 3];
const copiedNumbers = [ ...numbers ]; 
// this is how we add `spread opertor` in an array

console.log(numbers); // [1, 2, 3]
console.log(copiedNumbers); // [1, 2, 3 ]
  • Now lets try to update the original array(numbers) & log both the arrays to see if the copiedNumbers array also updated. Let us also check the equality between them.
numbers.push(4,5);

console.log(numbers); // [1, 2, 3, 4, 5]
console.log(copiedNumbers); // [1, 2, 3 ]

console.log(numbers === copiedNumbers); // false
  • Now we get different outputs, since their reference is different. It means we have successfully copied the original array without it's actual reference.
  • Hence, change in any of the arrays doesn't reflect to both the arrays.

array.slice()

const numbers = [ 1, 2, 3];
const copiedNumbers = numbers.slice();
// slice() returns shallow copy of some portion of array based upon the arguments passed to it.
numbers.push(4,5);

console.log(numbers); // [1, 2, 3, 4, 5]
console.log(copiedNumbers); // [1, 2, 3 ]

Cloning Objects :

  • There are 2 ways to shallow-clone / copy an object.
  1. Spread Operator
  2. Object.assign()

Spread Operator

  • Lets create an person object & copy the original object without it's reference.
const person = {
    name: 'Jon',
    age: 20,
};

const otherPerson = { ...person };

otherPerson.age = 21;

console.log(person); // {name : "Jon", age : 20 }
console.log(otherPerson); // {name : "Jon", age : 21 }

Object.assign() :

const value= { a: 2 };
const copiedValue= Object.assign({}, value);
// Object.assign() works same as the spread operator.

Deep Cloning :

  • Let's try creating a copy of that object, by adding nested obect to it.
const person = {
    firstName: 'Emma',
    car: {
        brand: 'BMW',
        color: 'blue',
        wheels: 4,
    }
};

const copiedPerson = { ...person };

person.firstName = "Watson";

console.log(person.firstName);
console.log(copiedPerson.firstName);
  • Cool, as always we both the objects retun distinct values.
  • Now lets try to change the value of the nested object (car) & see what that logs.
person.car.color = "red";

console.log(person.car.color);
console.log(copiedPerson.car.color);
  • Oops, We've got the same color red in both the objects(person & copiedPerson). This is because the spread operator just copies the first level of the object. That's why this method named shallow cloning.
  • So, to remove reference from the nested object, we have to again perform spread operation on the nested object(car). If there are n-number of nested objects, then we have to perform spread operation to all those nested objects as-well. This is Tricky right!!
  • So, to avoid that level of difficulty, we have Deep Cloning.
  • Deep Cloning can be achieved by using 2 methods.
  1. JSON.stringify()
  2. JSON.parse()

  3. Lets apply this in our previous person object & change the brand this time.

const person = {
    firstName: 'Emma',
    car: {
        brand: 'BMW',
        color: 'blue',
        wheels: 4,
    }
};

const stringifiedObject = JSON.parse(JSON.stringify(person));

person.car.brand = "AUDI";

console.log(person.car.brand); // "AUDI"
console.log(stringifiedObject.car.brand); // "BMW"
  • Hence, Now we get two different values of brand in nested object. Thus, Deep Cloning is helpful & easier for removing-reference-from-reference-types.

    Conclusion :

  • For the objects with one-level-nesting, spread operator will be okay, but for the objects with more than one level nesting Deep Cloning will be efficient for destroying all the references.

--> If you like this blog, feel free to like, comment & share with your friends.

--> If there is any thing to be corrected, plz mention in the comment section below, I would appreciate that.