Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating new variable from another

What would be the best way to create a new array or object from another. Since doing

 var oldvar = {x:1,y:2} //or [x,y]
 var newvar = oldvar

will link them, what would be the bets best way to clone or cope the new variable?

like image 496
ilyo Avatar asked Dec 27 '22 04:12

ilyo


1 Answers

Numbers in JavaScript

Numbers in JavaScript are what the spec calls 'Primitive Value Type'

From the specification about Numbers:

Number value # Ⓣ
primitive value corresponding to a double-precision 64-bit binary format IEEE 754 value.

NOTE A Number value is a member of the Number type and is a direct representation of a number.

So in your case newvar will be a copy of oldvar and not a reference.

In JavaScript, Number, Boolean, undefined, null or String are value types. When passing any of these 5 around, you are in fact passing values and not references, no need to clone those.

When you pass anything else around (Objects) need to use cloning since they are reference types.

When cloning objects in JavaScript there are two approaches.

Shallow Cloning

This means you clone 1 level deep. Assuming all our properties in the object are enumerable (This is usually the case if you haven't used property descriptors) you can use something like:

var a = {a:3,b:5}; 
var copy = {};
for(var prop in a){ 
 copy[prop] = a[prop];
}

However, often we want to copy the object's own properties and not everything it might inherit from its prototype, so we can do:

var copy = {};
for(var prop in a){
  if(a.hasOwnProperty(prop)){//check that the cloned property belongs to _a_ itself
     copy[prop] = a[prop];
  }
}

Note these two only shallow copy properties off a, they do not deal with setting the prototype, and clone all the properties by reference (Except properties who are primitive value types themselves :) ).

Deep copying

Deep copying means creating a clone of the object that is several levels deep. This calls to recursion since deep copying is defined as such (in psuedocode)

CopyObject(object)
    If object is a primitive value type
       return object
    clone := Empty Object
    For every member of object 
        Add CopyObject(member) as a property to clone 
    Return clone

We are applying the algorithm recursively on object properties of the clone.

Here is a sample implementation that I documented for you. It assumes ES5 (Chrome) but you can easily adapt it to other/older browsers. It does more stuff like treat Date and Regex like special cases. It also keeps a dictionary of properties it already handled so it will be able to handle circular references in an object. It is intended for learning and not for production use :) If you have any questions about it feel free.

var clone = function (a) {
    var passedRefs = [];     // Keep track of references you passed to avoid cycles
    var passedRefCreated = [];
    function clone2(a1) { // Inner function to handle the actual cloning
        var obj;
        if (typeof a1 !== "object" || a1 === null) { // Handle value type
            return a1;
        }
        var locInpPassed = passedRefs.indexOf(a1); // Detect circular reference
        if (locInpPassed !== -1) {
            return passedRefCreated[locInpPassed];
        }
        passedRefs.push(a1); // Add the object to the references to avoid circular references later
        if (a1 instanceof Date) { // Handle date and RegExp for special cases
            obj = new Date(a1.getTime());
        } else if (a1 instanceof RegExp) {
            obj = new RegExp(a1);
        }else if (Array.isArray(a1)){// handle arrays in order for Array.isArray to work. Thanks FizzyTea for catching this.
            obj = [];
        } else { // Create a new object with the prototype of the one we're cloning to support prototypical inheritance. Prototypes are _shared_
            obj = Object.create(Object.getPrototypeOf(a1));
        }
        passedRefCreated[passedRefs.indexOf(a1)] = obj; // Add to the references created dict
        Object.getOwnPropertyNames(a1).forEach(function (prop) { // Go through all the property, even the ones that are not enumerable
            obj[prop] = clone2(a1[prop]); // Call the algorithm recursively, just like in the pseudo code above
        });
        return obj;
    }
    return clone2(a); // Call the inner function that has access to the dictionary 
}

(For example, you can use a for... in loop to iterate through the properties).

like image 177
Benjamin Gruenbaum Avatar answered Jan 04 '23 20:01

Benjamin Gruenbaum