Prototypal inheritance and mutable objects

114 views
Skip to first unread message

Nick Fletcher

unread,
Mar 29, 2008, 3:00:54 AM3/29/08
to
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[i]) {
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[i]) {
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.

covex....@gmail.com

unread,
Mar 29, 2008, 4:46:49 AM3/29/08
to
I use my own functions for implementing the inheritance hierarchies.
May be it can be useful for you:

<script src="http://joos.googlecode.com/files/JooS.1.0.36.js"></
script>
<script>
var Collection = JooS.Reflect(JooS.Class, {


remove: function (item) {
var count = 0;
for (var i = 0, len = this.items.length; i < len; i++) {
if (item === this.items[i]) {
this.items.splice(i, 1);
i--;
len--;
}
}
}

});

JooS.Virtual(Collection, {


add: function (item) {
this.items[this.items.length] = item;
},

__constructor: function() {
this.items = [];
}
});


var Set = JooS.Reflect(Collection);
JooS.Virtual(Set, {
add: function (item) {


for (var i = 0, len = this.items.length; i < len; i++) {
if (item === this.items[i]) {
return;
}
}

this.add.__parent(item);
},
__constructor: function() {
this.__constructor.__parent();
this.otherArray = [];
}
});

A = new Set();
A.add(1);
A.add(2);
A.add(1);
console.log(A.items); // -> [1, 2];
</script>

Joost Diepenmaat

unread,
Mar 29, 2008, 7:08:10 AM3/29/08
to
Nick Fletcher <titosr...@gmail.com> writes:

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

The solution you outlined above is exactly what I do in similar
circumstances. Only I call the function "initialize" because it doesn't
create the object, it initializes it.

There isn't anything wrong with this approach.

> Thanks for your time.

Cheers,
Joost.

--
Joost Diepenmaat | blog: http://joost.zeekat.nl/ | work: http://zeekat.nl/

Richard Cornford

unread,
Mar 30, 2008, 1:35:59 PM3/30/08
to
Nick Fletcher wrote:
> I've recently been working on a fairly large JavaScript project
> using prototypal inheritance.

As javascript is a language that uses prototypal inheritance it could be
argued that if you use the language you are using prototypal
inheritance, and so that saying as much would be redundant.

> 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();
> }

Which is an example of the process that clearly expresses what is being
done, but is not particularly efficient as it creates a new - F -
function each time it is executed, but all of those - F - functions are
essentially identical. If this is to be done often then a more efficient
approach would be to only create a single - F - function and put it
where it could not be modified by external code. I.E.:-

var clone = (function(){
function F(){}
return (function(o){


F.prototype = o;
return new F();

});
})();

You have not said why you are using your - clone - function (why you
considered its use appropriate in your context and superior to the may
other approaches possible).

> The problem I've come across is that mutable objects in my
> "Prototype Object" get shared for all inheriting instances
> of the object.

Yes, that is what happens here. Indeed it is one of the aspects of this
strategy that make it useful as it avoids the need to explicitly assign
references to commonly shared object.

But this example is far too simplistic to justify the use of a - clone -
method as a simple constructor/prototype definition would get the job
done with a negligible difference in the complexity of the code used
(and could be argued to be clearer as it would be the more natural
approach when using javascript). I.E.:-

function Collection(){
this.items = [];
}
Collection.prototype.add = function(){
this.items[this.items.length] = item;
};
Collection.prototype.remove = function(){
var count = 0;
for (var i = 0, len = this.items.length; i < len; ++i) {


if (item === this.items[i]) {
this.items.splice(i, 1);

--i;
--len;
}
}
};

function List(){
this.items = [];
}
List.prototype = new Collection();
//or:- List.prototype = clone(Collection.prototype);
List.prototype.insert = function (item, index) {
this.items.splice(index, 0, item);
};
List.prototype.get = function (index) {
return this.items[index];
};

function Set(){
this.items = [];
}
Set.prototype = new Collection();
//or:- Set.prototype = clone(Collection.prototype);
Set.prototype.add = function (item) {


for (var i = 0, len = this.items.length; i < len; i++) {
if (item === this.items[i]) {
return;
}
}
this.items[this.items.length] = item;
};

var list = new List();
var set = new Set();

list.add("one");
alert(set.items.length === 0); // true, inevitably.

> 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

So you have an approach that does not employ a constructor function and
so does not get the set-up on instanceiation that comes with the -
new - operator and constructor functions, so you have added another
function that is used in place of the constructor. That seems to be
doing a lot of work to get back to what the language would have given
you form day one.

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

A good approach to what exactly? I use this style of cloning when I want
multiple object instances that share some sort of runtime determined
set-up or configuration so that they do not need to go through that
process again when each new instance is created, but then the object
being cloned is either never used as an actual instance or the cloning
process has to mask instance specific aspects of the original (requiring
a more elaborate cloning process). I do not use this approach for
inheritance relationships that could be statically defined in the source
code.

> What are my alternatives?

The alternatives depend on the 'why', but there will be hundreds (and
most of them, but probably not all of them, will be worse).

> Am I completely out of my mind?

Probably not (and certainly not for asking). However, keep a few facts
in mind; client-side javascript is vary rarely so complex that it needs
deep inheritance hierarchies, the language has a natural inheritance
mechanism that should not be deviated from until doing so has some
manifest advantage (that can be clearly stated), and that any single
'formal inheritance strategy' applied across the board is likely to act
as a straightjacket and preclude possibilities that could be useful
and/or interesting.

Richard.


Reply all
Reply to author
Forward
0 new messages