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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With