Reputation: 4185
How can I generate new instances of a class from an instance of that class?
Consider the next class and its instance:
// Create a new class
var Foo = function Foo () {
console.log('New instance of FOO!');
};
Foo.prototype.generate = function(first_argument) {
this.arr = [1,2,3,4,5];
};
var foo = new Foo();
foo.generate();
console.log(foo.arr); // [1,2,3,4,5]
Note that foo
is as an instance fo Foo
, and i'll be using this instance in the examples.
With Object.create I can generate new copies of the foo instance, but they will share the state data:
var bar = Object.create(foo);
console.log(bar.arr); // [1,2,3,4,5]
bar.arr.push(6);
console.log(foo.arr); // [1,2,3,4,5,6]
console.log(bar.arr); // [1,2,3,4,5,6]
And if some logic is in the constructor it is not called.
This is similar to the object create but it calls the constructor. It still has the same state data problem:
var Bar = function () {
foo.constructor.call(this);
};
Bar.prototype = foo;
var bar = new Bar();
console.log(bar.arr); // [1,2,3,4,5]
bar.arr.push(6);
console.log(foo.arr); // [1,2,3,4,5,6]
console.log(bar.arr); // [1,2,3,4,5,6]
Here is an example of what I exactly want to do, but it only works in Firefox:
// Create Bar class from foo
var Bar = foo.constructor;
Bar.prototype = foo.__proto__; // Firefox only
Here I have the class Bar that's a copy of the class Foo and I can generate new instances.
var bar = new Bar();
console.log(bar.arr); // undefined
bar.generate();
console.log(bar.arr); // [1,2,3,4,5]
Is there any other way to achive the same goal that works in all the browsers?
Upvotes: 4
Views: 4179
Reputation: 4399
Why not do this in the traditional sense? In C++ classes that can be copied or have instances created from them just implement a copy constructor. You can do this in JavaScript in the same way you do with C++, just have your constructor check arguments:
function Foo(from) {
if (from && from.prototype && from.prototype.constructor === Foo) {
this.arr = [].concat(from.arr);
}
}
Then create a copy method on Foo if you really want to:
Foo.prototype.createCopy = function() {
return new Foo(this);
};
Or just use the constructor:
var bar = new Foo(foo);
That will create a shallow clone of arr. To perform a deep clone, you need to write a deep clone method or use one from a JavaScript framework, like YUI.
If you want a new instance:
Foo.prototype.createNew = function() {
return new Foo();
};
var bar = foo.createNew();
var fooCopy = foo.createCopy();
If the class is likely to be used as a base class to another class, then use:
Foo.prototype.createNew = function() {
return new (this.prototype.constructor)();
};
Foo.prototype.createCopy = function() {
return new (this.prototype.constructor)(this);
};
If you are concerned about modifying the Foo class, you can always create your own implementation that emits the copy and create functions.
(function() {
var Foo = function() {},
MyNamespace = {
Foo: Foo
};
Foo.prototype = {
constructor: window.Foo,
createCopy: function() { /* ... */ },
createNew: function() { /* ... */ }
};
for (var n in window.Foo.prototype) {
Foo.prototype[n] = window.Foo.prototype[n];
}
// Export it.
window.MyNamespace = MyNamespace;
})();
If you want to just add these methods to the native Foo class, this is how you typically do it:
(function() {
if (! Foo.prototype.createCopy) {
Foo.prototype.createCopy = function() { /* ... */ };
}
if (! Foo.prototype.createNew) {
Foo.prototype.createNew = function() { /* ... */ };
}
})();
If you do this at the earliest invocation of your program/script, you will hook Foo before any instances have been created, giving you the ability to create a copy.
Upvotes: 2
Reputation: 19294
1) proto has to be avoided for obvious reasons, you have Object.getPrototypeOf to read an object's prototype in a standard way.
2)You don't have the constructor doing all the job, but rather have a kind of init method with generate.
So it would make much more sense to have a call to this.generate at the end of the constructor function.
This is why prototype inheritance doesn't work : the constructor does not its job.
In the same idea, you cannot blame Object.create since it does not neither the constructor, nor generate in your example, so you basically build an object having same prototype, that's all.
Provided you do add a call to this.generate to the constructor function, maybe the most easy way is to use the constructor property of foo to build a new object :
var bar = new foo.constructor();
which will give you a fresh new item.
So to answer your question : yes there's another way, using the constructor property of the object instance to build a new item.
But no inheritance scheme will be able to 'guess', that the full initialisation should be performed with such and such method. Initialisation is the job of the constructor. We might imagine for instance that you buil a new array this.arr = [];
in the constructor and that generate() will push() items inside this array, this would be a valid way. But you have to avoid adding properties through method, both for optimisation and clarity : setup all instance properties within the constructor, and maybe shared properties on the prototype, and only use those properties in the methods.
Upvotes: 5
Reputation: 76423
In light of your comment to Craig's answer: creating a new instance from any object can be easily done in a single line:
var newFoo = new (Object.getPrototypeOf(foo).constructor);
For some older browsers, Object.getPrototypeOf
won't work, though. You can easily fix this by adding this bit to your script:
if (!Object.getPrototypeOf)
{
Object.getPrototypeOf = function(o)
{
if (o instanceof Object)
{
return (o.__proto__ || o.prototype);
}
throw new TypeError('Object.getPrototypeOf called on non-object');
};
}
All things considered, in most cases:
var newFoo = new (foo.constructor);
works just fine, unless some joker messed with the instance, assigning it a constructor
property, or changed the prototype.
It looks to me as if you don't fully understand how JS resolves the context, and how references to properties of objects are resolved. Check the diagram in my answer here.
First off, change your last snippet to this:
Bar.prototype = Foo.prototype;//__proto__ is non-standard proto reference
Bar.prototype.constructor = Bar;
That should work X-browser.
As to why your other attempts failed, that's quite easy: when you write bar.arr
, JS simply looks for an arr
property directly attached to the instance. If that isn't found, the search is expanded to the Bar.prototye
. When that fails to, JS searches the prototype of Bar.prototype
, which is foo
(the instance passed to Object.create
, for example).
There, JS does find the property it was looking for, therefor bar.arr
references foo.arr
. If you change the object (Arrays are objects), it doesn't matter which reference was used to access the object. There is but one instance, which is kept in memory for as long as it is referenced at least once.
In full:
var Foo = function()
{
console.log('the Foo constructor');
};
Foo.prototype.generate = function()
{
this.arr = [1,2,3];
return this;
};
var Bar = function()
{
console.log('the Bar constructor');
};
Bar.prototype = new Foo;//make Bar inherit from Foo
Bar.prototype.constructor = Bar;//but make it use its own constructor
Now you can use both Foo
and Bar
as you wanted:
var foo = new Foo;
foo.generate();
foo.arr.push(4);
var bar = new Bar;
bar.generate();
console.log(foo.arr);//here [1,2,3,4]
console.log(bar.arr);//[1,2,3]
Of course, if you want to Bar
to start off with a particular instance of foo:
var foo = new Foo;
foo.generate();
Bar.prototype = foo;//
Bar.prototype.constructor = Bar;
var bar = new Bar;
console.log(bar.arr);//logs [1,2,3]
//BUT BE CAREFUL:
console.log(foo.arr === bar.arr);//true, bar only references the same property
bar.arr = bar.arr.slice(0);//copies the array
bar.arr.push(4);
console.log(foo.arr);//[1,2,3] still
console.log(bar.arr);//[1,2,3,4] changed
To undo the changes I've done above, I need only to unmask the prototype propery:
delete bar.arr;
console.log(bar.arr);//back to [1,2,3]
console.log(bar.arr === foo.arr);//true, I've deleted a bar property
That's it
Upvotes: 2
Reputation: 140238
Never put any data in the prototype, just functions.
You can achieve that pretty much out of the box with Object.create
:
function A() {
}
function B() {
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
Upvotes: 0
Reputation: 47119
In your second prototype example bar.prototype = foo
you will have to make a surrogate function so the constructor method ins't called and you truly extend from foo:
var Foo = function() {
console.log('foo init')
};
Foo.prototype.generate = function() {
this.arr = [1, 2, 3, 4];
}
var Bar = function() {
// Call the "constructor" method (Foo) with the correnct context and arguments,
// for this we can use Function.apply(context, array);
// where array can be any array like object as well a an real array:
Foo.apply(this, arguments);
console.log('bar init');
};
// Surrogate function:
var sur = function() {};
// Capture Foo's prototype:
sur.prototype = Foo.prototype;
// Apply the prototype from Foo to Bar's prototype chain, without initializing Foo:
Bar.prototype = new sur;
// Also remember to update the constructor:
Bar.prototype.constructor = Bar;
var f = new Foo(); // foo init;
var b = new Bar(); // foo init, bar init;
f.generate();
f.arr.push(-1);
b.generate();
b.arr.push(-2);
console.log(f.arr); // [1,2,3,4,-1]
console.log(b.arr); // [1,2,3,4,-2]
Upvotes: 0