Skip to content

Instantly share code, notes, and snippets.

@getify
Last active December 17, 2020 05:21
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save getify/4596011 to your computer and use it in GitHub Desktop.
Save getify/4596011 to your computer and use it in GitHub Desktop.
A native syntax alternative for `Function.prototype.bind`? This is an open exploration and soft proposal. Feedback appreciated. The main problem I'm trying to solve with `Function.prototype.bind()` is that it creates a new function that's wrapped around the function you specify. This means if you do that a lot, you're creating (and throwing away…
function foo() {
console.log(this.bar);
}
function doSomething(fn, overrideThis) {
if (overrideThis) fn.call(overrideThis);
else fn();
}
var bar = "bar1";
var obj1 = { bar: "bar2" };
var obj2 = { bar: "bar3" };
foo(); // "bar1"
foo.call(obj1); // "bar2";
var foo2 = foo.bind(obj1); // creates a whole new function... can be "ouch" for memory if done a lot
foo2 === foo; // false -- whole new function!
foo2(); // "bar2"
foo2.call(obj2); // "bar2" (not "bar3", `this` can't be overridden here)
doSomething( foo.bind(obj2) ); // "bar3"
doSomething( foo.bind(obj2), obj1 ); // "bar3" (`this` still can't be overriden)
// ATTENTION: new syntax proposal.
var foo3 = foo#obj1; // not a new function, just a special decorated reference to the same function
foo3 === foo; // true... same function!
foo3(); // "bar2" (defaulted `this` to `obj1`)
foo3.call(obj2); // "bar3" (`this` overridable, unlike Function.prototype.bind)
doSomething( foo#obj2 ); // "bar3"
doSomething( foo#obj2, obj1 ); // "bar2" (because `this` is still overridable)
@getify
Copy link
Author

getify commented Jan 23, 2013

@angus-c: good points, thanks. i like how you explained it better than how i explained it. ;-)

@juandopazo: as i said to @fearphage several times, I only said foo.bar === foo#bar to illustrate that there's not a new function being created, not that preserving that reference equality across === would even matter to me. That part I don't care about. The "no extra function wrapper" I do care about. Oh, and the re-bindability is a big thing IMHO.

@juandopazo
Copy link

The fact that I don't like soft bind for most uses doesn't mean I think it shouldn't be in Function.prototype. I don't use soft bind because I like the implicit contract "I will not change the value of this in this function". And that's because of a very common use case: event listeners. It's very common to pass listeners to event emitters which you haven't written yourself. For example:

// I'd like to add a listener and keep |this| pointing to the foo object
obj.addListener('someEvent', foo.listener.bind(foo));

But what if the EventEmmiter is implemented like this?

function EventEmitter() {
  //...
}
EventEmitter.prototype.emit = function () {
  for (var i = 0; i < this.listeners.length; i++) {
    // the author uses apply to pass multiple parameters to the listener
    // and calls apply, breaking my assumption about my listener if it was soft bound
    this.listeners[i].apply(this, arguments);
  }
};

@juandopazo
Copy link

If it isn't a new function, then foo.bar === foo#bar should be true. But in that case we are modifying the original and that can also break expectations. For instance:

var foo = {
  x: 10,
  bar: function () {
    console.log(this.x);
  }
};
var bar2 = foo#bar;
bar2(); // 10, all good
var o = {};
o.bar = foo.bar;
var bar3 = o#bar;
bar2(); // undefined!

It needs to be a new function, even if call/apply can still change this.

@getify
Copy link
Author

getify commented Jan 23, 2013

@juanpodazo my idea, conceptually, was that the #bar part of the reference assignment would add some sort of extra flag to the reference to indicate a default this binding to use when THAT SPECIFIC reference was later used in an invocation. That way the reference itself still pointed to the same underlying function, but the reference also had an extra payload along with it for specifying this.

var obj1 = {
   id: "obj1",
   foo: function() { console.log(this.id); }
};
var id = "X";
var obj2 = { id: "obj2" };
var foo2 = obj1.foo#obj2; // the "#obj2" flag only goes on the foo2 reference itself, not changing the function
foo2(); // "obj2"

var foo3 = foo2;
foo3(); // "X" --> foo2 is the only reference with the extra flag, but foo3 is just a normal reference to the function, not a reference to the reference 'foo2'. therefore, 'foo3' just works in a standard, non-special way. 'foo2' is the only special reference here.

In this respect, I'd be totally fine if the special decorated reference foo2 wasn't strictly === to the unspecial normal reference foo3, even though both point at the same function. The foo2 reference would have this extra flag on him which would make him distinct from foo3. But there'd still only be one underlying function that both of them pointed at.

@juandopazo
Copy link

Ok so you're thinking about references as some sort of "object".I don't know much about VMs and how they work, but how is a "reference object" different from a new function in terms of memory?

I think of variables and references in JS as keys in an object. I conceptualize them as spaces in memory that contain an ID of some sort that points to another piece of memory which contains the object itself. I'd have to check the specification, but I don't think messing with references as such is a good idea. Let's say you can do it. It would mean having either a special kind of reference or a flag in all references. I don't think there's much difference between the two. It would add a runtime cost (to check if it's a specific type or if it has a flag) and/or a memory cost to every reference. That sounds bad.

@getify
Copy link
Author

getify commented Jan 23, 2013

Via @juanpodazo http://wiki.ecmascript.org/doku.php?id=strawman:soft_bind

That's obviously part of what I'm thinking here (in terms of the re-bindability). But my biggest concern is that I don't think we should have to create a whole new wrapper function to express a this binding. Again, my use case was doing LOTS of binding so it's obvious that even small amounts of memory churn/GC from that could quickly add up.

The existing Function.prototype.bind() could be changed to return these "this-defaulted references" (or whatever you want to call them), instead of a whole new function reference. But the obvious legacy breakage side effects would rule that out.

That strawman suggests another API, like Function.prototype.softBind(). While it as proposed doesn't suit my needs, if that new API could instead return these special references that behave as I suggest, there wouldn't be any need for the special # operator jazz.

@getify
Copy link
Author

getify commented Jan 23, 2013

@juanpodazo I suggested a flag on a reference as a way to explain it conceptually. I highly doubt that would influence the implementation at all. In fact, I'm terribly inept at speaking to that, as how VM's work is far beyond my understanding.

It could just be a special kind of reference that refers back to where it was assigned and does a late-binding (examines the object reference at call time to use it). I suspect there are probably other ways too.

Even if it WERE a special kind of reference that had one extra reference on it (so double the memory usage of a reference), that has to be WAY smaller than the memory usage of a full function implementation, right?

Also, just because there's this implementation detail to figure out (I punt because I don't know even remotely how the best way is) regarding how references could remember something like this, doesn't mean that the solution would require the other way around (as you were originally saying) that it would be making a change to the function object itself. That kind of thing is way the other direction from what I'm thinking.

@juandopazo
Copy link

What I meant is that my guess is that they'd have to add a flag for all references, not just references to bound functions, which would impact a lot more than just bound functions. Of course it'd be nice if you could get feedback from someone who writes language implementations.

What I can say is:

  • If there was a way to optimize bindings, I'm sure engine implementors will eventually do it for foo.bind(bar). It looks optimizable up to a certain level because it doesn't have anything in its internal scope, so no scope object, just a copy of the previous function with a different |this| value.
  • Messing with references adds an extra level of complexity to the language. As a developer references are something I don't remotely want to think about.
  • If foo.bar !== foo#bar and typeof foo#bar === 'string' then I'm going to assume it's a new function. Whether it's a new function or a magic reference, that is an implementation detail.

@getify
Copy link
Author

getify commented Jan 24, 2013

@juanpodazo:

If there was a way to optimize bindings, I'm sure engine implementors will eventually do it for foo.bind(bar)

If the VM's were able to transparently make it so a new function wasn't actually being created, that'd be great, but I think it might be problematic in the converse way to how my proposal is being seen as possibly problematic.

For "legacy" reasons (if we can call ourselves in a post-ES5 era yet!), bind() has to return a function reference that's different than the original function, even if the VM is able to go so far as to literally reuse the same function in both instances. Maybe this reference faking/breaking is easy for VM's, maybe not. Not really sure. But could be an issue for them. I'm not sure how much of a perf win, if any, we'd get if the VM still had to create (and later GC) a shell function ("wrapper") just for maintaining these itinerant this bindings.

Moreover, their bind() optimization, even if they worked out that concern, wouldn't, as we've said, address re-bindability, because again for legacy reasons, bind() would have to return non-rebindable refs at the least. softBind would still be necessary.

I would obviously like to see both problems addressed, and if they can be addressed by the same mechanism, I'd consider that preferable.

Messing with references adds an extra level of complexity to the language. As a developer references are something I don't remotely want to think about.

I agree, we shouldn't complicate how references work.

But this would surely be an opaque change to references. It wouldn't be an observable (or mutable) "property" or anything like that. It's just simply a way of preserving with a reference the intended this binding. JS already has several other mechanisms for specifying this, so I'm not sure that adding some rule/mechanism into that mix would really make references across the board any harder for devs to handle.

Whether it's a new function or a magic reference, that is an implementation detail.

Totally agreed. Not sure what you meant by "string" though. Typo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment