Search This Blog

Sunday, March 4, 2018

Understanding Immutable,Mutable,Deep and Shallow Copy in Javascript

Immutable:
    An immutable object is an object whose state cannot be modified after it is created.Examples of native JavaScript values that are immutable are numbers and strings.

    consider following code


    let a = 'test';
    let b = a;
    a = a.substring(2);

    console.log(a);
    console.log(b);
    console.log(a === b) ;

    output:
        st
        test
        false

    Here when we assign variable a to b, b gets value of a but they do not refer to same object so changes in variable a does not reflect in variable b.

    consider one more code snippet

        let a = 1;
        let b = a;
        a++;

        console.log(a)
        console.log(b)
        console.log(a === b)
    output:
        2
        1
        false

    After assigning variable 'a' to 'b' we are incrementing variable 'a' but value of variable 'b' do not get incremented as variable 'a' & 'b' referer to different memory location.


Mutable:
    A mutable object is an object whose state can be modified after it is created.
    Examples of native JavaScript values that are mutable include objects, arrays, functions, classes, sets, and maps.

    consider following code

        let a = {
            foo: 'bar'
        };

        let b = a;

        a.foo = 'test';

        console.log(b.foo);
        console.log(a.foo);
        console.log(a === b);

    Output:
        test
        test
        true

    Though we changed 'a.foo' changes reflected in 'b.foo'.    variable a & b both references one and same so change in state of one reflect in other as both refer to same object.

    consider one more code snippet:

        const person = {
            name: 'John',
            age: 28
        }
        const newPerson = person
        newPerson.age = 30
        console.log(newPerson === person) // true
        console.log(person) // { name: 'John', age: 30 }

    output:

        true
        {name: "John", age: 30}


    Here we changed 'newPerson.age' and changes reflected in 'person.age' as they refer same object again.This is unexpected & undesired.
    if you don't want this you must create a new object and explicitly copy over the properties.

    var person = {
            name: 'John',
            age: 28
    };

    var newPerson = new Object();
    for(prop in person){
      newPerson[prop] = person[prop];
    }

    newPerson.age = 30
    console.log(newPerson === person)
    console.log(person)
    console.log(newPerson)

    output:
        false
        {name: "John", age: 28}
        {name: "John", age: 30}

    Here we copied all properties of object 'person' to 'newperson' object but both do not refer to same memory location ,so changes in one do not reflect in other.

How can we copy only value of one object to another rather than creating another reference to same object ?

    Object.assign is ES6 feature that comes to our rescue.

    Lets consider following code snippet:

        const person = {
          name: 'John',
          age: 28
        }
        const newPerson = Object.assign({}, person, {
          age: 30
        })
        console.log(newPerson == person)
        console.log(newPerson === person)
        console.log(person)
        console.log(newPerson)

    output:
        false
        false
        {name: "John", age: 28}
        {name: "John", age: 30}

When object has nexted object then deep copy fails

http://msdotnetbuddy.blogspot.com/2018/09/objectassign-in-javascipt.html

deep copy should be done with JSON.parse(JSON.stringify(obj2clone)) or using lodash-->cloneDeep() method.

Mutating Array:
    consider following code snippet

        const characters = [ 'Obi-Wan', 'Vader' ]
        const newCharacters = characters
        newCharacters.push('Luke')
        console.log(characters === newCharacters)

        output:
            true

    Here also both 'characters' array & 'newCharacters' array refer to same object.

ES6 contains a spread operator:

    with reference to previous code snippet,Es6 comes with spread feature.It is denoted by '...'.With spread ,array 'newCharacters'
    get all elements of array 'characters' and additional  array element 'Luke'.

    modified code snippet:

        const characters = [ 'Obi-Wan', 'Vader' ]
        const newCharacters = [ ...characters, 'Luke' ]
        console.log(characters === newCharacters)        
        console.log(characters)
        console.log(newCharacters)
    output:

        false
        ["Obi-Wan", "Vader"]
        ["Obi-Wan", "Vader", "Luke"]

    Here 'characters' array & 'newCharacters' array do not refer to same object.


    using spread operator on object:
   
    following code snippet

        const person = {
            name: 'John',
            age: 28
        }
        const newPerson = {
            ...person,
            age: 30
        }
        console.log(newPerson === person) // false
        console.log(newPerson) // { name: 'John', age: 30 }

    Output:
        false
        {name: "John", age: 30}
   
    Here object 'newPerson' get all properties of 'person' and additional property 'age',but 'person' already have 'age' property so object 'newPerson' get only one property with latest value i.e. 30.

Freezing Object from modification:

    consider following code snippet

        const object1 = {
          property1: 42
        };

        const object2 = Object.freeze(object1);

        object2.property1 = 33;
        console.log(object2.property1);

    output:
        42

    The Object.freeze() method freezes an object meaning it, prevents new properties from being added to it; prevents existing
    properties from being removed; and prevents the prototype from being changed.The method returns the passed object.

    Any attempt to do so fails sliently except when code is running in strict mode with 'use strict' on top,lets see the same 

        'use strict'
        const object1 = {
          property1: 42
        };

        const object2 = Object.freeze(object1);

        object2.property1 = 33;
        console.log(object2.property1);

    output:
        Uncaught TypeError:Cannot assign to read only property 'property1' of object.


How to check if object is frozen ?

    var obj = {
      foo: 'bar',
      pass:'none'   
    };

    obj.lumpy = 'woof';

    delete obj.pass;

    var o = Object.freeze(obj);

    obj.foo = 'quux';
    o.foo = 'quux';

    console.log(obj);
    console.log(o);

    Object.isFrozen(obj);

    delete obj.foo;
    console.log(obj);

output:
    {foo: "bar", lumpy: "woof"}
    {foo: "bar", lumpy: "woof"}

    in 'Object.freeze' Both the object being passed as well as the returned object are frozen.
    attempt to delete property with    'delete obj.foo;' failed silently.

Freezing an array

    Consider following code snippet

        let a = [0,5,7];
        Object.freeze(a);

        a[0]=1;
        //a.push(2);
        console.log(a);
    output:
        we get following error for line in above code
            a.push(2);
        Uncaught TypeError: Cannot add property 1, object is not extensible but line 'a[0]=1;' fails silently.

what is difference between object.seal & object.freeze ?

Object.seal:
    It prevents adding and/or removing properties from the sealed object ,however defined properties still can be changed.

    It throws a TypeError when attempting to modify in strict mode else fails silently.Using delete will return false.

    It makes every property non-configurable, and they cannot be converted from data accessors to properties and vice versa.

Object.freeze:

    Exactly what Object.seal does but additionally it prevents changing any existing properties too.

Object.preventExtensions:

    The Object.preventExtensions() method prevents new properties from ever being added to an object.

    consider following code snippet

        const object1 = {'srno':1,'deleteme':'yes'};
        Object.preventExtensions(object1);
        object1.srno =2;

        try {
          Object.defineProperty(object1, 'property1', {
            value: 42
          });
        } catch (e) {
          console.log(e);
        }
        delete object1.deleteme;
        console.log(object1);

    output:
        Attempt to define new property with 'defineProperty' result in error 'TypeError: Cannot define property property1 object is not extensible'.

      At the end object1 has value {srno: 2}.As deleting property succeed in line 'delete object1.deleteme;' as its not prevented here.Similarly modifying existing property value is allowed too hence 'srno' property becomes '2' .

       
Shallow copy:

    In shallow copy only the memory address is copied.so new object also refer to same memory address any change in property of one reflect in other.

    consider following code snippet:

        var employeeDetailsOriginal = {  name: 'Manjula', age: 25, Profession: 'Software Engineer' };
        var employeeDetailsDuplicate = employeeDetailsOriginal;
        employeeDetailsDuplicate.name = 'NameChanged';
        console.log(employeeDetailsOriginal)
    output:

        {name: "NameChanged", age: 25, Profession: "Software Engineer"}

Deep Copy:

    A deep copy copies all fields, and makes copies of dynamically allocated memory pointed to by the fields.
   
    Suppose you want to make a deep copy of object X into variable Y,With Deep Copy ,it makes a copy of all the members of X, allocates different memory location for Y
    and then assigns the copied members to Y to achieve deep copy. In this way, if X vanishes Y is still valid in the memory.

    consider following code snippet

        var employeeDetailsOriginal = {  name: 'Manjula', age: 25, Profession: 'Software Engineer' };

        var employeeDetailsDuplicate = JSON.parse(JSON.stringify(employeeDetailsOriginal));

        employeeDetailsDuplicate.name = 'NameChanged';
        console.log(employeeDetailsOriginal)
        console.log(employeeDetailsDuplicate)
    output:
        {name: "Manjula", age: 25, Profession: "Software Engineer"}
        {name: "NameChanged", age: 25, Profession: "Software Engineer"}

    Here change in 'employeeDetailsDuplicate.name' does not reflect in 'employeeDetailsOriginal.name' as both point to different memory location.our trick below made deep copy of object concerned

        var employeeDetailsDuplicate = JSON.parse(JSON.stringify(employeeDetailsOriginal));

    Point to note is in Shallow copying only new reference is created for existing object but old & new copy point to same object.

On performance point of view deep copying create new object ,creating new objects is time and memory consuming ,it do comes with a bit more overhead but advantages outweigh.

    Making sure your state is immutable forces you to think better of your application structure.Reduces the possibility of nasty bugs.you can just rely on oldObject === newObject to check if state changed or not, this is way less CPU demanding.

No comments:

Post a Comment