How to create descendants of Array class?

T

Tom de Neef

I know how to define new methods for Array objects, like:

Array.prototype.append = function(element)
// Add element to the end of the array
{
this[this.length] = element
}

append(x) will be a valid for any array. But (to structure my code) I would
like to introduce methods for a descendant class of Array, so that I can use
specialArray.doSomething()
where doSomething() is defined only for arrays of class specialArray.

I can't find a way of doing this but I'm sure one of you experts can give me
a hint.
TIA
Tom
 
L

Lasse Reichstein Nielsen

Tom de Neef said:
But (to structure my code) I would
like to introduce methods for a descendant class of Array, so that I can use
specialArray.doSomething()
where doSomething() is defined only for arrays of class specialArray.

There are no classes in Javascript. You can make an object that inherits
from another object that inherits from Array.prototype. It won't help
you though.

function clone(object) {
var cloner = function(){};
cloner.prototype = object;
return new cloner();
}

// "extend" Array:
function SpecialArray(){ Array.call(this); }
SpecialArray.prototype = clone(Array.prototype);
SpecialArray.prototype.constructor = SpecialArray;

This will create something that sortof extends Array. However, it
won't work like an array.
I can't find a way of doing this but I'm sure one of you experts can give me
a hint.

Hint: give up.
The special behavior of the "length" property of arrays only works
for objects that are created using the Array constructor or equivalent.

If you create a new constructor, SpecialArray, with a prototype that
inherits from Array.prototype, then you won't get the special Array
behavior. It'll just be a normal object with some methods.

/L
 
R

Richard Cornford

Tom said:
I know how to define new methods for Array objects, like:

Array.prototype.append = function(element)
// Add element to the end of the array
{
this[this.length] = element
}

append(x) will be a valid for any array. But (to structure
my code) I would like to introduce methods for a descendant
class of Array, so
that I can use specialArray.doSomething()
where doSomething() is defined only for arrays of class
specialArray.

I can't find a way of doing this but I'm sure one of you
experts can give me a hint.

Javascript has no 'classes' (or it only has one 'class') so when you speak
of classes is relation to javascript what you would usually be doing is
talking about emulating the concept of 'class' in javascript, and there are
many way to achieve that emulation.

Because there is only one type of object (the native ECMAScript object) in
javascript the way in which objects may be classified is by taking note of
the modifications that they undergo (usually when created); objects that
undergo 'like' modifications may be regarded as begin instances of the same
'class'.

Some of the modifications that an object undergoes in association with its
creation are automatic, such as the assignment of a value to the objects
internal [[Prototype]] property when it is created vial the - new - operator
applied to a function, and others are programmed (i.e. with the code inside
a constructor).

There are many ways creating object instances that have undergone
sufficiently 'like' modifications for them to be regarded as instances of a
'class'. The most obvious is the use of the - new - operator with a function
that is to be used as a constructor, along with modifications to the -
prototype - property of that constructor:-

function AnObject(x){
thisx = x;
}
AnObject.prototype.aMethod = function(){
return this.x;
}

var instance = new AnObject(3);

But a 'factory' function approach is just as viable (if at least a little
less efficient):-

function getAnObject(x){
var obj = new Object();
obj.x = x;
obj.aMethod = forAnObjectMethod;
}
function forAnObjectMethod(){
return this.x;
}

var instance = new getAnObject (3);

The above has (apart from a few minor details) the same outcome as the use
of the constructor above. However, the 'factory' approach can get round some
limitations of the 'constructor' method. This is particularly useful in with
regard to Array objects. What makes an Array special is that it is an
instance of the native ECMAScript object that has a special internal [[Put]]
method, but because that method is internal it cannot be modified by JS
code, and it is not inherited through the prototype chain. So doing
something like:-


function AnObject(x){
thisx = x;
}
AnObject.prototype = new Array();

var instance = new AnObject(3);

-does not result in an object that inherits the Array's special [[Put]]
method (even if it does inherit all the other methods of an array), because
the resulting object will have its own [[Put]] method and that will be the
normal object [[Put]] method. The 'factory' approach gets around this
problem because it creates a real Array and then modifies that Array
instance:-
function getAnObject(x){
var obj = new Array(); // or better var obj = [];
obj.x = x;
obj.aMethod = forAnObjectMethod;
}
function forAnObjectMethod(){
return this.x;
}

var instance = new getAnObject (3);

This object is a modified Array and so has the Array's special [[Put]]
method.

A similar process can be applied to function objects as will, giving the
possibility of having instances of a 'class' that are also callable (and you
will not see that in many other languages).

Another role for 'factory'-like functions is adding interfaces to other
objects. This can be analogous to 'subclassing' in some uses. So we might
have the previous augmented array object and 'subclass' it by passing (some
of) its instances through another function:-

function addSomeInterface(obj){
obj.anInterfaceMethod = forAnInterfaceMethod;
}
function forAnInterfaceMethod(){
return this.y;
}

var instance = addSomeInterface(new getAnObject (3));

Richard.
 
T

Thomas 'PointedEars' Lahn

Lasse said:
Hint: give up.
The special behavior of the "length" property of arrays only works
for objects that are created using the Array constructor or equivalent.

If you create a new constructor, SpecialArray, with a prototype that
inherits from Array.prototype, then you won't get the special Array
behavior. It'll just be a normal object with some methods.

Therefore, why not use the following?

function Array2()
{
this.items = new Array();
for (var i = arguments.length; i--;)
{
this.items = arguments;
}
}

Array2.prototype = {
get: function(p) {
return this.items[p];
},

set: function(p, v) {
return (this.items[p] = v);
},

call: function(m) {
return this.items[m].apply(this.items,
Array.prototype.slice.call(arguments, 1));
},

toString: function() {
return this.items.toString();
},

valueOf: function() {
return this.items;
},

foo: function(bar)
{
return this.items.push(bar, 42);
}
};

var a = new Array2(1, /2/, [3]);
window.alert(a.call("splice", 1, 2));
window.alert(a.get("length"));
window.alert(a.set("length", 5));
window.alert(a.foo(23));
window.alert(a);
window.alert(new Array("1337").concat(a));

If you can't beat 'em, eat 'em ;-)


PointedEars
 
T

Tom de Neef

Richard Cornford wrote
<snip>
function getAnObject(x){
var obj = new Array(); // or better var obj = [];
obj.x = x;
obj.aMethod = forAnObjectMethod;
return obj;
}

Thank you! This is exactly what I was looking for: an array with its own
methods.
I can now implement (e.g.) thisArray.loadFromFile(fName) and
thatArray.loadFromFile(fName) with their own functionality.
In this way I hope to get a bit more structure in my code (which, with 40
years of strongly typed languages behind me, I dearly miss).
Tom
 
T

Thomas 'PointedEars' Lahn

Richard said:
[...]
Another role for 'factory'-like functions is adding interfaces to other
objects. This can be analogous to 'subclassing' in some uses. So we might
have the previous augmented array object and 'subclass' it by passing (some
of) its instances through another function:-

function addSomeInterface(obj){
obj.anInterfaceMethod = forAnInterfaceMethod;
}
function forAnInterfaceMethod(){
return this.y;
}

var instance = addSomeInterface(new getAnObject (3));

However, this "factory" approach (a factory is usually considered to be
something different than this) has the obvious drawback as compared to
object aggregation that the augmented object has enumerable properties that
show up in a for-in loop and similar operations, thereby also excluding
certain identifiers from being used as a property. Whereas the aggregated
(Array) object (although just aggregated through a reference to it by the
aggregating object's property) would allow clean for-in iteration of the
aggregated (Array) object, and would not impose further restrictions on
property names.

The iteration drawback could only be mitigated -- hard to maintain `if'
statements in the iteration loop aside -- by providing an iterator method
that filters out the properties that are not to be included in the
iteration, e.g.:

function addSomeInterface(obj)
{
obj.anInterfaceMethod = forAnInterfaceMethod;
obj.iterator = interfaceIterator;
}

function interfaceIterator()
{
var o = new Object();

for (var p in this)
{
// or equivalent
switch (p)
{
case "anInterfaceMethod":
case "iterator":
break;

default:
o[p] = 1;
}
}

return o;
}

var a = [];
addSomeInterface(a);

for (var p in a.iterator())
{
// ...
}

However, one drawback of the "factory" approach cannot be worked around: in
contrast to object aggregation, it is not reasonably applicable to host
objects because of their allowed arbitrary [[Put]] implementations.


PointedEars
 
R

Richard Cornford

Thomas 'PointedEars' Lahn said:
Richard said:
[...]
Another role for 'factory'-like functions is adding interfaces
to other objects. This can be analogous to 'subclassing' in
some uses. So we might have the previous augmented array
object and 'subclass' it by passing (some of) its instances
through another function:-

function addSomeInterface(obj){
obj.anInterfaceMethod = forAnInterfaceMethod;
}
function forAnInterfaceMethod(){
return this.y;
}

var instance = addSomeInterface(new getAnObject (3));

However, this "factory" approach (a factory is usually considered
to be something different than this)

You are at liberty to propose a better label for a function/method that
is intended to create instances of a 'class'.
has the obvious drawback as compared to object aggregation
that the augmented object has enumerable properties that
show up in a for-in loop and similar operations, thereby also
excluding certain identifiers from being used as a property.

Which is not a drawback in comparison to adding methods to the
prototype, as those method become enumerable and so have the same
impact, or in comparison to any object acting in the role of an instance
of a 'class' as such objects will tend to have a set of specific
properties added to their instances or prototypes which are also
enumerable. The result is that it is usually unhelpful/impractical to
apply a for-in loop to an object acting in the role or instance of a
'class', and so the consequences of any particular example having
additional enumerable properties are insignificant.

The augmented array facilities direct indexed looping and maintains its
'magic' - length - property. If that is what is wanted from the object
then it can be achieved, and achieved reasonably easily/efficiently.
Whereas the aggregated (Array) object (although just
aggregated through a reference to it by the aggregating
object's property) would allow clean for-in iteration of the
aggregated (Array) object,

Which may be valuable if the array was going to be used as a sparse
array (for efficiency). The vast majority of arrays are not sparse.

On the other hand, the situation of the array as an internal detail of
another abject has the drawback that all access to, and interactions
with, the array would suggest a wrapping layer of method calls. Slowing
the whole process down and introducing at least some additional function
objects into the system.
and would not impose further restrictions on
property names.

I don't see the relevance of that.
The iteration drawback could only be mitigated -- hard to
maintain 'if' statements in the iteration loop aside --
by providing an iterator method that filters out the
properties that are not to be included in the
iteration,

The iteration drawback would not need to be mitigated if the object were
not intended to be the subject of for-in loops.
However, one drawback of the "factory" approach cannot be
worked around: in contrast to object aggregation, it is not
reasonably applicable to host objects because of their
allowed arbitrary [[Put]] implementations.

True.

Richard.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top