N
Nick Fletcher
I've recently been working on a fairly large JavaScript project using
prototypal inheritance. I've been using this commonly seen clone
(a.k.a. object) function for implementing the inheritance hierarchies:
function clone(o) {
function F() {}
F.prototype = o;
return new F();
}
The problem I've come across is that mutable objects in my "Prototype
Object" get shared for all inheriting instances of the object. For
example, a rudimentary collections API:
/**
* Collection Prototype Object
*/
var Collection = {
items: [], // Gets used by everyone
add: function (item) {
this.items[this.items.length] = item;
},
remove: function (item) {
var count = 0;
for (var i = 0, len = this.items.length; i < len; i++) {
if (item === this.items) {
this.items.splice(i, 1);
i--;
len--;
}
}
}
};
/**
* An indexed collection
*/
var List = clone(Collection);
List.insert = function (item, index) {
this.items.splice(index, 0, item);
};
List.get = function (index) {
return this.items[index];
};
/**
* Contains a unique set of elements (no duplicates)
*/
var Set = clone(Collection);
Set.add = function (item) {
for (var i = 0, len = this.items.length; i < len; i++) {
if (item === this.items) {
return;
}
}
this.items[this.items.length] = item;
};
Any cloned objects that I create will all share the same 'items'
array.
var list = clone(List);
var set = clone(Set);
list.add("one");
alert(set.items.length === 0); // false, it's 1
One solution would be for all cloned objects to explicitly set their
own copies of the mutable objects, which can be a bit of a bother
(especially if there are a lot).
My current working solution is to add a 'create' method to the root of
my inheritance hierarchy. This method does the job of cloning "this"
and adding any mutable objects to the cloned object:
var Collection = {
create: function () {
var collection = clone(this);
collection.items = []; // add mutable objects here
return collection;
},
// add and remove
};
Now I can create as many instances as I want and they will all have
their own mutable objects. For example:
var list = List.create();
var set = Set.create();
list.add("one");
alert(set.items.length === 0); // true
There's still the problem of having to attach all super mutable
objects to an inheriting object if you want to add another mutable
property:
Sub = clone(Collection);
Sub.create = function () {
var collection = clone(this);
collection.items = [];
collection.otherArray = [];
return collection;
};
This could be quite annoying if you have a lot of mutable objects that
need to be added (which I think is true without this pattern anyway).
In order to avoid duplicating all that code again, a (not so clean)
possibility is a pseudo-super call using apply:
Sub = clone(Collection);
Sub.create = function () {
var sub = Collection.create.apply(this); // Attach other
mutable objects
sub.otherArray = [];
return sub;
};
My questions are: Is this a good approach? What are my alternatives?
Am I completely out of my mind?
Thanks for your time.
prototypal inheritance. I've been using this commonly seen clone
(a.k.a. object) function for implementing the inheritance hierarchies:
function clone(o) {
function F() {}
F.prototype = o;
return new F();
}
The problem I've come across is that mutable objects in my "Prototype
Object" get shared for all inheriting instances of the object. For
example, a rudimentary collections API:
/**
* Collection Prototype Object
*/
var Collection = {
items: [], // Gets used by everyone
add: function (item) {
this.items[this.items.length] = item;
},
remove: function (item) {
var count = 0;
for (var i = 0, len = this.items.length; i < len; i++) {
if (item === this.items) {
this.items.splice(i, 1);
i--;
len--;
}
}
}
};
/**
* An indexed collection
*/
var List = clone(Collection);
List.insert = function (item, index) {
this.items.splice(index, 0, item);
};
List.get = function (index) {
return this.items[index];
};
/**
* Contains a unique set of elements (no duplicates)
*/
var Set = clone(Collection);
Set.add = function (item) {
for (var i = 0, len = this.items.length; i < len; i++) {
if (item === this.items) {
return;
}
}
this.items[this.items.length] = item;
};
Any cloned objects that I create will all share the same 'items'
array.
var list = clone(List);
var set = clone(Set);
list.add("one");
alert(set.items.length === 0); // false, it's 1
One solution would be for all cloned objects to explicitly set their
own copies of the mutable objects, which can be a bit of a bother
(especially if there are a lot).
My current working solution is to add a 'create' method to the root of
my inheritance hierarchy. This method does the job of cloning "this"
and adding any mutable objects to the cloned object:
var Collection = {
create: function () {
var collection = clone(this);
collection.items = []; // add mutable objects here
return collection;
},
// add and remove
};
Now I can create as many instances as I want and they will all have
their own mutable objects. For example:
var list = List.create();
var set = Set.create();
list.add("one");
alert(set.items.length === 0); // true
There's still the problem of having to attach all super mutable
objects to an inheriting object if you want to add another mutable
property:
Sub = clone(Collection);
Sub.create = function () {
var collection = clone(this);
collection.items = [];
collection.otherArray = [];
return collection;
};
This could be quite annoying if you have a lot of mutable objects that
need to be added (which I think is true without this pattern anyway).
In order to avoid duplicating all that code again, a (not so clean)
possibility is a pseudo-super call using apply:
Sub = clone(Collection);
Sub.create = function () {
var sub = Collection.create.apply(this); // Attach other
mutable objects
sub.otherArray = [];
return sub;
};
My questions are: Is this a good approach? What are my alternatives?
Am I completely out of my mind?
Thanks for your time.