Reputation: 8661
This question is a follow-up to this one. For some reason I'm coming back to JS after 7 years and boy, I can hardly recognize the dear old beast.
Purely for educational purpose, I decided to rewrite naive implementations of the various things Function.prototype.bind()
allows to do. It's just an exercise to try to understand what's going on and spark a few questions.
I would be happy to stand corrected for all the mistakes and misunderstandings in my examples and comments.
Also, this code is not mine. I got the idea from a blog and only slightly tweaked it, but unfortunately I lost track of the source. If anyone recognizes the original, I'll be happy to give due credit. Meanwhile, I apologize for the blunder.
The initial idea is simply to do what lambda calculus savvies apparently call a "partial application", i.e. fixing the value of the first parameters of a function, that also accepts an implicit "this" first parameter, like so:
Function.prototype.naive_bind = function (fixed_this, ...fixed_args) {
const fun = this; // close on the fixed args and the bound function
return function(...free_args) { // leave the rest of the parameters free
return fun.call(fixed_this, ...fixed_args, ...free_args);
}
}
class class1 {
constructor (p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
}
var innocent_bystander = { "huh?":"what?" }
var class2 = class1.naive_bind(innocent_bystander);
class2 (1,2) // exception: class constructor must be invoked with new (as expected)
console.log(new class2(1,2)) // exception: class constructor must be invoked with new (Rats!)
function pseudoclass1 (p1, p2) {
this.p1 = p1;
this.p2 = p2;
}
var pseudoclass2 = pseudoclass1.naive_bind(innocent_bystander);
pseudoclass2 (1,2) // same exception (as expected)
console.log(new pseudoclass2(1,2)) // same again (at least it's consistent...)
Tonnerre de Brest ! Apparently the runtime is not happy with a mere wrapper based on Function.prototype.call()
.
It seems the real bind()
is adding the dollop of secret sauce needed to give the generated function the appropriate "constructor" flavour (Apparently by "constructor" the ECMA 262 specification does not mean a class constructor, but any function that can be invoked with "new" and use "this" to populate the properties and methods of a freshly created object)
var purefunction1 = console.log
var purefunction2 = purefunction1.naive_bind (null, "Sure,", "but")
purefunction2 ("you can still", "bind pure", "functions")
// sure, but you can still bind pure functions
// ...and make animals talk (by binding simple methods)
var cat = { speech: "meow" }
var dog = { speech: "woof" }
var fish= { speech: "-" }
var talk = function(count) { console.log ((this.speech+", ").repeat(count-1) + this.speech + "!") }
talk.naive_bind (cat,1)(); // meow!
talk.naive_bind (dog,1)(); // woof!
talk.naive_bind (cat)(3) // meow, meow, meow!
talk.naive_bind (fish)(10) // -, -, -, -, -, -, -, -, -, -!
// and bind arrow functions
// ("this" is wasted on them, but their parameters can still be bound)
var surprise = (a,b,c) => console.log (this.surprise, a,b,c)
var puzzlement = surprise.naive_bind(innocent_bystander, "don't", "worry");
// "this" refers to window, so we get our own function as first output.
surprise ("where", "am", "I?") // function surprise(a, b, c) where am I?
// the bound value of this is ignored, but the other arguments are working fine
puzzlement("dude") // function surprise(a, b, c) don't worry dude
Apparently, everything works as expected. Or did I miss something?
We apparently can't get away with passing a wrapper to new
, but we can try invoking new
directly.
Since the value of this
is provided by new
, the construction-specialized wrapper has only to worry about real constructor parameters.
Function.prototype.constructor_bind = function (...fixed_args) {
const fun = this;
return function(...free_args) {
return new fun (...fixed_args, ...free_args);
}
}
var class1_ctor = class1.constructor_bind(1);
console.log (class1_ctor(2)) // class1 { p1:1, p2:2 }
var monster = (a,b) => console.log ("boooh", a, b)
var abomination = monster.constructor_bind(1);
console.log (abomination(2)) // exception: fun is not a constructor (as expected)
Well, that seems to cut it. I imagine the real bind()
is far safer and faster, but at least we can reproduce the basic functionalities, namely:
this
to methodsedit: questions removed to comply with SO policy.
Replaced by just one, the one that matches the title of this post, and that I tried to explore by providing a naive and possibly erroneous equivalent of what I thought the real function did:
Please help me understand how the native Function.prototype.bind()
method works, according to the version 6.0 of the ECMAScript specification.
Upvotes: 0
Views: 52
Reputation: 664970
The only bit you have been missing is the introduction of new.target
in ES6, which a) makes it possible to distinguish between [[call]] and [[construct]] in a function
and b) needs to be forwarded in the new
call.
So a more complete polyfill might look like this:
Function.prototype.bind = function (fixed_this, ...fixed_args) {
const fun = this;
return function(...free_args) {
return new.target != null
? Reflect.construct(fun, [...fixed_args, ...free_args], new.target)
: fun.call(fixed_this, ...fixed_args, ...free_args);
}
}
Some other details would involve that fun
is asserted to be a function object, and that the returned bound function has a special .name
, an accurate .length
, and no .prototype
. You can find these things in the spec, which apparently you've been reading already.
Upvotes: 1