I was going through "JavaScript the definitive guide" and I encountered this auxiliary constructor (below their Set
definition):
// Definition of Set, taken from chapter 9.6.1
function Set() {
this.values = {};
this.n = 0;
this.add.apply(this, arguments);
}
Set.prototype.add = function() {
for(var i = 0; i < arguments.length; i++) {
var val = arguments[i];
var str = Set._v2s(val);
if (!this.values.hasOwnProperty(str)) {
this.values[str] = val;
this.n++;
}
}
return this;
};
Set._v2s = function(val) {
switch(val) {
case undefined: return 'u';
case null: return 'n';
case true: return 't';
case false: return 'f';
default: switch(typeof val) {
case 'number': return '#' + val;
case 'string': return '"' + val;
default: return '@' + objectId(val);
}
}
function objectId(o) {
var prop = "|**objectid**|";
if (!o.hasOwnProperty(prop))
o[prop] = Set._v2s.next++;
return o[prop];
}
};
Set._v2s.next = 100;
// Taken from 9.6.7: auxiliary constructor
function SetFromArray(a) {
Set.apply(this, a);
}
SetFromArray.prototype = Set.prototype;
var s = new SetFromArray( [1,2,3] );
console.log(s instanceof Set); // => true
The text then goes further to say:
In ECMAScript 5, the bind() method of functions has special behavior that allows it to create this kind of auxiliary constructor.
What special behavior is being referred to here and how it can be used to create that kind of auxiliary constructor? Can an auxiliary constructor even be created in JavaScript?
I tried recreating this function using the bind() method:
function SetFromArray(a) {
Set.bind(null).apply(this, a);
}
But then, I realised that in order to leverage the bind()
method's behavior in which the boundFunction inherits the prototype of the original when used as a constructor, I would have to use the boundfunction as the constructor in the constructor invocation, so the SetFromArray
function I defined may not work the same way as the one defined without bind
.
What special behavior is being referred to here and how it can be used to create that kind of auxiliary constructor?
It is not evident from that single phrase we find at the end of that chapter, but I don't think the authors intended to use bind
as you did in your attempt, because:
SetFromArray
longer while still keeping the same code that was already there.bind
, which means you could have just used apply
or call
instead. bind
comes to use in scenario's where you don't immediately call the function it returns, but pass it to some other piece of code (like with return
), which then may call it.Note how the authors wrote
"...that allows it [i.e. bind
] to create this ... constructor", with which they seem to suggest that you could call bind
in a way to get the desired constructor as return value. So they hint at something like:
var SetFromArray = <something>.bind(<something>);
Although this sounds promising at first sight -- as bind
returns a function --
there are several reasons why this is not possible, including:
When the function returned by bind
is called, an array argument is not spread into separate arguments, as apply
does, yet that transformation is the whole point of having the SetFromArray
function in the first place.
If we wanted a workaround for the above issue, we would still need apply
to be called, resulting in something like:
var SetFromArray = Function.apply.bind(<something>);
But apply
is not a constructor, and so new SetFromArray([])
will raise an exception. And even if it were a constructor, it would not create an instance of Set
.
In strict mode (which in modern day developments should be turned on), bind
returns a function that is not a constructor, trashing the whole idea that bind
would create a constructor for us.
The book was first published in 1996, while ECMAScript 5 only got published in 2009. The 6th edition of JavaScript: The Definitive Guide came out in 2011, having in its introduction
The sixth edition covers HTML5 and ECMAScript 5
The phrase you quoted from chapter 9 is such an addition that was made in that context. I doubt the authors/editors put that added claim to the test, and merely thought "hey, here we have another function that just passes on some arguments... let's add they can use bind
to give it a modern ECMAScript 5 flavor!"
Since 2011 a lot has changed:
Set
is now part of native ECMAScript. None of the quoted code is needed anymore. We can create a new set with new Set([1,2,3])
.new MyConstructor(...myArray)
.class
and extends
, reducing reasons why you would still want to tinker with prototype
properties.Symbol
, making things like prop = "|**objectid**|"
needlessThe idea of providing auxiliary constructors through tampering with prototype chains looks contrary to good OOP design. If we look how ECMAScript itself solves this "problem", we can find the following examples:
Array.from
and Array.of
provide alternative ways to create Array
instances.Object.create
and Object.fromEntries
provide alternative ways to create plain objects.This suggests that in ECMAScript static functions are the standard way to provide alternative ways to create instances of your class.