I totally missed the ES6 revolution and I'm returning to JavaScript after 7 years, to find a host of very strange things happening.
One in particular is the way Function.prototype.bind()
handles class constructors.
Consider this:
// an ES6 class
class class1 {
constructor (p) {
this.property = p;
}
}
var class2 = class1.bind(passer_by);
var class3 = class2.bind(passer_by,3);
class2() // exception, calling a constructor like a function
class3() // idem
console.log (new class1(1)) // class1 {property: 1}
console.log (new class2(2)) // class1 {property: 2}
console.log (new class3() ) // class1 {property: 3}
// An ES5-style pseudo-class
function pseudoclass1 (p) {
this.property = p;
}
var property = 0;
var passer_by = { huh:"???" }
var pseudoclass2 = pseudoclass1.bind(passer_by);
var pseudoclass3 = pseudoclass1.bind(passer_by,3);
pseudoclass1(1); console.log (property) // 1 (this references window)
pseudoclass2(2); console.log (passer_by) // Object { huh: "???", property: 2 }
pseudoclass3() ; console.log (passer_by) // Object { huh: "???", property: 3 }
console.log (new pseudoclass1(1)) // pseudoclass1 {property: 1}
console.log (new pseudoclass2(2)) // pseudoclass1 {property: 2}
console.log (new pseudoclass3() ) // pseudoclass1 {property: 3}
Apparently class2
and class3
are identified as constructors, and class3
is a partial application of class1
that can generate instances with a fixed value of the first parameter.
On the other hand, though they can still act as (poor man's) constructors, the ES5-style functions are indeed served the value of this
set by bind()
, as can be seen when they act on the hapless passer_by
instead of clobbering global variables as does the unbound pseudoclass1
.
Obviously, all these constructors somehow access a value of this
that allows them to construct an object. And yet their this
are supposedly bound to another object.
So I guess there must be some mechanism at work to feed the proper this
to a constructor instead of whatever parameter was passed to bind()
.
Now my problem is, I can find bits of lore about it here and there, even some code apparently from some version of Chrome's V8 (where the function bind() itself seems to do something special about constructors), or a discussion about a cryptic FNop function inserted in the prototype chain, and, if I may add, the occasional piece of cargo cult bu[beep]it.
But what I can't find is an explanation about what is actually going on here, a rationale as to why such a mechanism has been implemented (I mean, with the new spread operator and destructuring and whatnot, wouldn't it be possible to produce the same result (applying some arguments to a constructor) without having to put a moderately documented hack into bind()
?), and its scope (it works for constructors, but are there other sorts of functions that are being fed something else than the value passed to bind()
?)
I tried to read both the 2015 and 2022 ECMA 262 specifications, but had to stop when my brains started leaking out of my ears. I traced back the call stack as:
19.2.3.2
9.4.1.3
9.4.1.2
7.3.13
where something is said about constructors, in a way: "If newTarget is not passed, this operation is equivalent to: new F(...argumentsList)". Aha. So this pseudo-recursive call should allow to emulate a new
somehow... Erf...
I'd be grateful if some kind and savvy soul could give me a better idea of what is going on, show me which part(s) of the ECMA specs deal with this mechanism, or more generally point me in the right direction.
I'm tired of banging my head against a wall, truth be told. This bit of Chrome code that seems to indicate bind()
is doing something special for constructors is just incomprehensible for me. So I would at least like an explanation about it, if everything else fails.