views:

746

answers:

5

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?

A: 

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;
   }
}
Alex
Sorry Alex but this doesn't solved my problem with dynamically created objects. Your approach is pretty similar to my clone function.
+1  A: 

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
SolutionYogi
this prototype is my case an undefined property, because object is already created. I can't remember that this.prototype property could exist in an initialised “CLASS”. If you would like to get the prototype from already initialised “CLASS” you have to use arguments.callee.prototype instead. Consequentially you are using the prototype of the static object instead of the current dynamic object.
A: 

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.

Rojo
Yep, this works fine for static object but not dynamic. Imagine you have an reference to an DOM element in it.
What situation do you envision that would entail BAR (a constructor) having a reference to a DOM object?
Rojo
A: 
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`
Christoph
Sorry Christoph the same problem. In my case I have to extend the Bar Object dynamically.
A: 

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.?

Anyway thanks to anyone how did help me!