SitePoint Sponsor

User Tag List

Results 1 to 3 of 3
  1. #1
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Deep Clone an Object

    I came across an example of a shallow copy script last night, and decided I would set myself the task of coming up with a deep copy version.

    For one thing I wanted to use recursion, a technique that I'm still trying to get on top of.

    My first stage. It was pretty evident that it needed streamlining.
    Code JavaScript:
    var deepClone = function(o, clone) {
      var i = 0, prop, len,
      clone = clone || (o instanceof Array ? [] : {});
     
      if (o instanceof Array && (len = o.length)){
        for (; i < len; i+=1){
    	  clone.push ((o[i] instanceof Object && o[i].constructor != Function)
    	    ? deepClone(o[i], (o[i] instanceof Array ? [] : {}))
    	    : o[i]
    	  )
    	}
      } else if (o instanceof Object) {
        for (prop in o){
    	  if (o.hasOwnProperty(prop)){
    	    clone[prop] = (o[prop] instanceof Object && o[prop].constructor != Function)
    	      ? deepClone(o[prop], (o[prop] instanceof Array ? [] : {}))
    	      : o[prop];
    	  }
    	}
      }
      return clone;
    };

    I've so far managed to get it down to this
    Code JavaScript:
    var deepClone = function(o) {
      if (o instanceof Object && o.constructor != Function){
        var clone = o instanceof Array ? [] : {}, prop;
     
    	for (prop in o){
    	  if (o.hasOwnProperty(prop)){ 
    	    clone[prop] = (o[prop] instanceof Object) 
    	      ? deepClone(o[prop]) 
    	      : o[prop]; 
    	  }
    	}
        return clone;
      }
      return o;
    };

    and this is a working example
    Code JavaScript:
    // ------ An object with various properties, subproperties etc -------
    var obj = {
    	propA : 5,
    	propB : [ 1, 3, 5, { 
    	    val1 : 3, 
    		val2 : 5, 
    		val3 : 7, 
    		valArray : [ 9, 11, 13 ] 
    	  }
    	],
    	propC : { 
    	  fruit1 : 'apples', 
    	  fruit2 : 'bananas', 
    	  fruit3 : 'apricots', 
    	  fruit4 : 'grapes',
    	  fruitObj : {
    	    miscFunc : function(){ alert (this.fruit1); return this.fruit4;},
    		vegArray : [ 'cabbage', 'carrots', 'mushrooms', { 
    		    seasoning1: 'salt',
    			seasoning2: 'pepper'
    		  } 
    		]
    	  }
    	},
    	propD : 'Just a string',
    	propE : new RegExp('^[a-zB-Z]+$','gi')
    };
    // -------------------------------------------------------------------------
     
    var deepClone = function(o) {
      // If o is a function or a primitive return it
      // Check if o is an object or an array object and set clone accordingly
      if (o instanceof Object && o.constructor != Function){
        var clone = o instanceof Array ? [] : {}, prop;
     
      // Check to see if property is an own property
      // hasOwnProperty responds true on Array properties
      // If the property is an Object then call deepClone( to process it and return )
      // otherwise assign the primitive value
      // finally return the clone
    	for (prop in o){
    	  if (o.hasOwnProperty(prop)){ 
    	    clone[prop] = (o[prop] instanceof Object) 
    	      ? deepClone(o[prop]) 
    	      : o[prop]; 
    	  }
    	}
        return clone;
      }
      return o;
    };
     
    // --------- A quick test to see if we have copies or references ---
    var cloneObj = deepClone(obj);
    obj.propC.fruitObj.vegArray[2] = 'cauliflower';
    console.log(cloneObj.propC.fruitObj.vegArray[2]); // mushrooms
     
    cloneObj.propB[2] = 22;
    console.log(obj.propB[2]); //5
     
    console.log(obj);
    console.log(cloneObj);

    I've just thrown in a RegExp object and noticed it fails, so I have to address that.

    I would be interested in any feedback. Are there any glaring mistakes with my method or improvements that could be made?

    Another technique I'm interested in is memoization. I know it can be used effectively for things like factorials, but would it be feesible for something like a deep copy?

    Any thoughts from those in the know would be greatly appreciated

    RLM

  2. #2
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Right I'm here with this little exercise.
    Code JavaScript:
    var deepClone = function(o) {
      if (o.constructor === Array || {}.toString.apply(o) === '[object Object]'){
        var clone = (o.constructor === Array) ? [] : {}, prop;
     
    	for (prop in o){
    	  if (o.hasOwnProperty(prop)){ 
    	    clone[prop] = (o[prop] instanceof Object) 
    	      ? deepClone(o[prop]) 
    	      : o[prop]; 
    	  }
    	}
        return clone;
      }
      return o;
    };

    I've been working through the type checks. At one point I went for 'o.constructor === Object', however for obvious reasons this fails if you want to clone a prototype object. I then remembered [object Object].

    Code:
    if (o.constructor === Array || {}.toString(o) === '[object Object]'){
      var clone = (o.constructor === Array) ? [] : {}, prop;
    Could it be improved? Is it flawed? Is there a string method that returns [array Array]?

    I'm sure there are other questions.

    Edit: Again it's flawed and fails on other types of object like a RegExp.

    Edit2: Simple fix.
    Instead of {}.toString(o) === '[object Object]' it should be {}.toString.apply(o) === '[object Object]'

    RLM

  3. #3
    SitePoint Addict
    Join Date
    Nov 2008
    Location
    Thailand
    Posts
    278
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just coming back to this.

    I think the answer is yes it's flawed. A guy on another forum had a go at a similar exercise and made less of a meal of it than I did.

    The main problem with my code comes down to type checking and the use of 'instanceof Object'

    The problem is that 'instanceof Object' returns true for functions, which means my code is passing functions into the recursice function call.

    typeof ... == 'object', seems to be a better alternative.
    Code:
    var fn = function(){var x = 5;}
    var xOb = {};
    
    console.log (typeof fn == 'object'); // false
    console.log (typeof xOb == 'object'); // true
    console.log (fn instanceof Object); // true
    Question: Functions are first class objects. You can add properties to functions just like other objects. I haven't tested this, but I'm now wondering whether they should in fact be passed through. Over complicating things maybe.

    Anyway

    Update 1:
    Code JavaScript:
    var deepClone = function(o) {
        var clone = (o.constructor === Array) ? [] : {}, prop;
     
        for (prop in o){
          if (o.hasOwnProperty(prop)){
            clone[prop] = (typeof o[prop] == 'object')
              ? deepClone(o[prop])
              : o[prop];
          }
        }
        return clone;
    };

    I wanted to try and utilise this in my literal object extend code, and due to the fact I need to pass 'this' from my constructor the extend needs to take 2 parameters. Parent and Child. I believe what I have ended up with here is a pretty generic version.

    Update 2:
    Code JavaScript:
    var deepCopy = function(parent, child){
    	for(var prop in parent) {
    	  if (parent.hasOwnProperty(prop))
            child[prop] = (typeof parent[prop] === 'object') 
    	    ? deepCopy(parent[prop], (parent[prop].constructor === Array)? [] : {}) 
    	    : parent[prop];      
        }
        return child;
    };

    RLM


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •