Question

I know it is really annoying to read this topic again. Before you start diggin into the code, one solution could be that I don't get prototypes and objects in JavaScript. But at this point i think, i do.

The problem is:
How to clone an JavaScript Class (created with prototypes), so that the “cloned” Class remains untouched when extending and executing afterwards?

function clone(obj){
  if(obj == null || typeof(obj) != 'object')
      return obj;

  var temp = new obj.constructor();
  for(var key in obj)
      temp[key] = clone(obj[key]);

  return temp;
}

var FOO = function() {
  var myBAR = clone(BAR);
  myBAR.prototype = jQuery.extend(true, myBAR.prototype, this); // deep cloning twice and extending with this
  console.log("FOO:", this.name);
  new myBAR();
};

FOO.prototype = {
  name: "FOO"
};

var BAR = function() {
  console.log("BAR:", this.name);
};

BAR.prototype = {
  name: "BAR"
};

new FOO(); // returns FOO: FOO and BAR: FOO 
new BAR(); // returns BAR: FOO should return BAR: BAR

If i've got it right, the second call of new BAR() (after new FOO()) should return BAR: BAR not BAR: FOO as at the moment.

One possible solution for this problem is an complete rewrite of the clone function in something like this:

function clone(obj) {
  return eval("("+obj.toString()+")"); // the same as eval(uneval(obj));
}

But this approach has an BIG downside, you can't pass any dynamically created objects.

Any ideas?

Was it helpful?

Solution 5

I just wanted to show anyone my solution for the problem above.

function cloneClass (func) {

  if(typeof func == 'function') {

    var prototype = {};
    for (var prop in func.prototype) {
      prototype[prop] = func.prototype[prop];
    };

    var Class = function() {
      var _this = this;
      var constructor = function() {
        func.apply(_this, arguments);
      };
      constructor.prototype = _this;

      for (var property in func) {
        if(property != "prototype") {
          constructor[property] = func[property];
        }
      };

      return constructor;
    };

    Class.prototype = prototype;
    return new Class();
  }

  return func;

};

Try to dig into it to understand how this is working. Does anyone can see any problems with this implementation, memory leaks etc.?

OTHER TIPS

The problem is how you are cloning the 'prototype'

The following line

myBAR.prototype = jQuery.extend(true, myBAR.prototype, this); // deep cloning 

You are not only cloning the 'prototype', you are also cloning the 'name' property.

If you replace above line with

myBAR.prototype = jQuery.extend(true, myBAR.prototype, this.prototype); // deep cloning 

Your code will now return

new FOO(); // returns FOO:FOO and BAR:BAR
new BAR(); // returns BAR:BAR

The issue with cloning a javascript object is how deep do you decide to go?

Consider i have the following object:

var obj = { 
    prop : {
        n1prop : {
            hello : 'world';
        }
    }
};

This means i would have to traverse all properties that are of type 'object' which could get quite expensive if you have a deep nesting.

If you have a simple 1 level object you could just use a simple reflection for loop and create a new object literal. NOTE: This won't copy methods of the original object.

function clone(obj) {
   var cloned;

   if (obj && typeof obj === 'object') {
       cloned = {};
       for (var p in obj) {
           if (obj.hasOwnProperty(p) && typeof obj[p] !== 'function') {
               cloned[p] = obj[p]
           }
       }
       return cloned;
   }
   else {
       return null;
   }
}

There is an another change that needs to be made, in addition to that mentioned by SolutionYogi. In FOO, you're passing BAR to be cloned, but BAR is constructor (typeof BAR == "function"), so it's going to fail the first test the clone function, and your return value will be a reference to an unchanged BAR. And that means myBAR.prototype is not a clone of BAR.prototype, but a reference to it.

In order to actually create a new constructor, not just a ref, I think you'll have to use eval--adding something like:

if (typeof obj == "function) {
    eval("var temp = " + obj + ";");
    return temp;
}

There are other considerations (as Alex points out), but adding the above, should cause your test case to be successful.

function clone(obj) {
    if(typeof obj !== 'undefined') {
        clone.prototype = Object(obj);
        return new clone;
    }
}

function Foo() {} // base class

function Bar() {} // derived class
Bar.prototype = clone(Foo.prototype); // inherit from `Foo.prototype`
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top