Does Array.apply work as expected?

D

Dolaameng

I am reading Nicholas's book <Professional JavaScript for Web
Developers> 2nd. On page 168, he explained the "parasitic constructor
pattern" by an example as follows,

/////////////////////////////////////////////////////
function SpecialArray(){
//create the array
var values = new Array();
//add the values
values.push.apply(values, arguments);
//assign the method
values.toPipedString = function(){
return this.join("|");
};
//return it
return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"
/////////////////////////////////////////////////////

The code works perfectly. But I was just wondering if I can implement
the SpecialArray as an inheritance of the built-in Array, using a
classical inheritance pattern. So I came up with two versions of
implementations, which unfortunately, both failed.

My first implementation is like this,
///////////////////My Implementation 1///////////////
function SpecialArray(){
Array.apply(this,arguments);
}
function Temp(){}
Temp.prototype=Array.prototype;
SpecialArray.prorotype=new Temp();
SpecialArray.prototype.constructor=SpecialArray;
SpecialArray.prototype.toPipedString=function(){
return this.join('|');
};
//Test cases
var arr=new SpecialArray(1,2,3);
console.log('constructor: '+arr.constructor); //
Correct: points to SpecialArray
console.log('instanceof specialArray: '+(arr instanceof
SpecialArray)); //Correct: TRUE
console.log('instanceof array: '+(arr instanceof Array)); //
Incorrect: FALSE
console.log('toPipedString: '+arr.toPipedString()); //ERROR:
this.join is not a function
/////////////////////////////////////////////////////
Here my intention was to call the Array constructor in the
SpecialArray constructor, and point SpecialArray's prototype to an
object instantiated by a Temp constructor, whose prototype is set to
be Array's prototype. However the code fails as shown in the commnets
in the testing code beneath.

I suspected that the error was caused by the use of Temp constructor,
so I changed the code and this time I linked the SpecialArray's
prototype directly to an Array instance, like this,

///////////////////My Implementation 2///////////////
function SpecialArray(){
Array.apply(this,arguments);
}

SpecialArray.prorotype=new Array(); //directly linked to an array
instance without Temp
SpecialArray.prototype.constructor=SpecialArray;
SpecialArray.prototype.toPipedString=function(){
return this.join('|');
};

var arr=new SpecialArray(1,2,3);
console.log('constructor: '+arr.constructor); //
Correct: points to SpecialArray
console.log('instanceof specialArray: '+(arr instanceof
SpecialArray)); //Correct: TRUE
console.log('instanceof array: '+(arr instanceof Array)); //
Correct: TRUE
console.log('toPipedString: '+arr.toPipedString()); //
Incorrect: an empty string
/////////////////////////////////////////////////////

This time the testings work fine except that arr.toPipedString()
returns an empty string. I suspect this is because the "this" instance
was not correctly initialized by calling "Array.apply(...)" in
SpecialArray's constructor.
I did a little search online, and found that Array.apply(...) will
RETURN a proper array, but this doesnt help here because we cannot
write the line like
this=Array.apply(null,arguments);
in the constructor, right? Some other guys online also mentioned that
"myClass.apply(something,args) will fail in many cases especially if
called on native classes like Date and Number.". I am not sure of
this.

So somebody please help telling me what was wrong with my code and how
to fix them accordingly? thanks a lot!
 
D

David Mark

Dolaameng said:
I am reading Nicholas's book <Professional JavaScript for Web
Developers> 2nd. On page 168, he explained the "parasitic constructor
pattern" by an example as follows,

/////////////////////////////////////////////////////
function SpecialArray(){
//create the array
var values = new Array();
//add the values
values.push.apply(values, arguments);
//assign the method
values.toPipedString = function(){
return this.join("|");
};
//return it
return values;
}

Well, that's a pretty lousy "constructor". It constructs nothing, but
returns an augmented array.
var colors = new SpecialArray("red", "blue", "green");

Clueless (and a obviously terrible example for beginners).
alert(colors.toPipedString()); //"red|blue|green"

Yes, it "works" in that it creates and augments an array.
/////////////////////////////////////////////////////

The code works perfectly. But I was just wondering if I can implement
the SpecialArray as an inheritance of the built-in Array, using a
classical inheritance pattern. So I came up with two versions of
implementations, which unfortunately, both failed.

You can't do it as you can't simulate the [[put]], which can mutate the
length property.
My first implementation is like this,
///////////////////My Implementation 1///////////////
function SpecialArray(){
Array.apply(this,arguments);
}


function Temp(){}
Temp.prototype=Array.prototype;

Too late (see below).
SpecialArray.prorotype=new Temp();

Typo. And why is this "constructor" calling itself?
SpecialArray.prototype.constructor=SpecialArray;
SpecialArray.prototype.toPipedString=function(){

You are setting these properties too late. The prototype has already
been copied. And what is Temp supposed to be?
return this.join('|');
};
//Test cases

You aren't ready for that yet. And, as mentioned, "sub-classing" the
Array constructor is not practical in JS.
var arr=new SpecialArray(1,2,3);
console.log('constructor: '+arr.constructor); //
Correct: points to SpecialArray
console.log('instanceof specialArray: '+(arr instanceof
SpecialArray)); //Correct: TRUE
console.log('instanceof array: '+(arr instanceof Array)); //
Incorrect: FALSE
console.log('toPipedString: '+arr.toPipedString()); //ERROR:
this.join is not a function
/////////////////////////////////////////////////////
Here my intention was to call the Array constructor in the
SpecialArray constructor, and point SpecialArray's prototype to an
object instantiated by a Temp constructor, whose prototype is set to
be Array's prototype. However the code fails as shown in the commnets
in the testing code beneath.

You can't test what you don't yet understand. Put down that silly book. ;)
I suspected that the error was caused by the use of Temp constructor,
so I changed the code and this time I linked the SpecialArray's
prototype directly to an Array instance, like this,

///////////////////My Implementation 2///////////////
function SpecialArray(){
Array.apply(this,arguments);
}

This is the first one that actually looks like a constructor.
SpecialArray.prorotype=new Array(); //directly linked to an array

Typo, again.
instance without Temp

There is no classical inheritance in JS and therefore no classes or
instances. There are constructors and constructed objects.
Understanding the difference is key.
SpecialArray.prototype.constructor=SpecialArray;
SpecialArray.prototype.toPipedString=function(){
return this.join('|');
};

var arr=new SpecialArray(1,2,3);
console.log('constructor: '+arr.constructor); //
Correct: points to SpecialArray
console.log('instanceof specialArray: '+(arr instanceof
SpecialArray)); //Correct: TRUE
console.log('instanceof array: '+(arr instanceof Array)); //
Correct: TRUE
console.log('toPipedString: '+arr.toPipedString()); //
Incorrect: an empty string

And what is - this - set to when you call arr.toPipedString? Hint: it's
not an Array object.

You would have to preserve the Array object as a private member, which
removes the need to inherit from an Array object (which isn't practical
anyway).
/////////////////////////////////////////////////////

This time the testings work fine except that arr.toPipedString()
returns an empty string. I suspect this is because the "this" instance
was not correctly initialized by calling "Array.apply(...)" in
SpecialArray's constructor.

The - this - identifier is set on each call. There is no initialization.
I did a little search online, and found that Array.apply(...) will
RETURN a proper array, but this doesnt help here because we cannot
write the line like
this=Array.apply(null,arguments);
in the constructor, right?

It is unlikely anything good would come of that.
Some other guys online also mentioned that
"myClass.apply(something,args) will fail in many cases especially if
called on native classes like Date and Number.". I am not sure of
this.

The Number constructor does not act as a constructor when called without
the - new - operator. Same for String. I can't remember what Date does
off-hand, but all it takes is a glance at the ECMA specs to find out.
I'd trust that before I'd trust "some guys online". Remember that even
the book (and library) authors are guessing roughly half the time (.500
is considered expert level in this industry).

You are definitely barking up the wrong tree(s). You don't inherit from
native objects, you extended their prototypes or wrap them with objects
that inherit _directly_ from Object.prototype (not by way of a
Array.prototype).
So somebody please help telling me what was wrong with my code and how
to fix them accordingly? thanks a lot!

First take that book back. It's not helping you. Then realize that
inheriting from an Array object is impractical for this language. Then
come up with another idea for your first attempt (preferably unrelated
to classical inheritance patterns).
 
L

Lasse Reichstein Nielsen

Dolaameng said:
I am reading Nicholas's book <Professional JavaScript for Web
Developers> 2nd. On page 168, he explained the "parasitic constructor
pattern" by an example as follows,

/////////////////////////////////////////////////////
function SpecialArray(){
//create the array
var values = new Array();
//add the values
values.push.apply(values, arguments);
//assign the method
values.toPipedString = function(){
return this.join("|");
};
//return it
return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"
/////////////////////////////////////////////////////

I would call it a "factory" instead of "constructor", but otherwise
understandable.
The code works perfectly. But I was just wondering if I can implement
the SpecialArray as an inheritance of the built-in Array, using a
classical inheritance pattern. So I came up with two versions of
implementations, which unfortunately, both failed.

You can't. The "magic" of an array's "length" property only works on
objects that are created using the Array constructor (or array literal
syntax). These are the ones that have the hidden [[Class]] property
as "Array"

If you could change Array.prototype, you could put another element
in the chain. However, Array.prototype isn't writable.

If you find a JavaScript implementation with a writable __proto__
or similar, you can add a link to the prototype chain after the
object has been created. However, that's not part of any standard.

All in all, you can't do what you are trying to.

/L
 
D

Dolaameng

Dolaameng said:
I am reading Nicholas's book <Professional JavaScript for Web
Developers> 2nd. On page 168, he explained the "parasitic constructor
pattern" by an example as follows,
/////////////////////////////////////////////////////
function SpecialArray(){
    //create the array
    var values = new Array();
    //add the values
    values.push.apply(values, arguments);
    //assign the method
    values.toPipedString = function(){
        return this.join("|");
    };
    //return it
    return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"
/////////////////////////////////////////////////////

I would call it a "factory" instead of "constructor", but otherwise
understandable.
The code works perfectly. But I was just wondering if I can implement
the SpecialArray as an inheritance of the built-in Array, using a
classical inheritance pattern. So I came up with two versions of
implementations, which unfortunately, both failed.

You can't. The "magic" of an array's "length" property only works on
objects that are created using the Array constructor (or array literal
syntax). These are the ones that have the hidden [[Class]] property
as "Array"

If you could change Array.prototype, you could put another element
in the chain. However, Array.prototype isn't writable.

If you find a JavaScript implementation with a writable __proto__
or similar, you can add a link to the prototype chain after the
object has been created. However, that's not part of any standard.

All in all, you can't do what you are trying to.

/L

Got it. Thanks! It turns out that it is the typo that breaks the
instanceof test in the first implementation. And as you guys
explained, it is not a good idea to write code like "Array.apply
(this,arguments);" in an constructor. Doing that simply results in
something like an empty array, which causes the following "join"
invocation to return an empty string.
 

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,763
Messages
2,569,562
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top