24

I'm getting my head wrapped about currying and other techniques using Function.prototype.bind.
It seems extremely useful to change function scope (i.e., this value) in certain situations.

However it looks like you can't change the scope with bind once you already did so:

function f = obj.method.bind(42); 
function g = obj.method.bind('Hi');

function f2 = f.bind('Hi'); // “this” is still 42

Is it possible to retrieve the original unbound function from a bound function at all?

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • @Martin: I'm creating a fiddle right now. It seems like a very simple question to someone who knows the answer, hence why I posted a question without prior research. – Dan Abramov May 09 '12 at 19:51
  • @Martin: I just [checked my assumptions](http://jsfiddle.net/dXq2E/) and it really looks like I can't change the scope with `bind` twice. So, is it possible at all? Is there some kind of `unbind`? – Dan Abramov May 09 '12 at 20:02
  • Yup, I'm with Felix on this one - no way, José – Alnitak May 09 '12 at 20:15
  • I don't see how it would be possible, but then again, I can't come up with a situation where it would ever be needed. – Martin Jespersen May 09 '12 at 20:18

3 Answers3

27

What the bind method basically does is something like (not exactly, because arguments are sliced to exclude the context):

function bind(context) {
    var self = this;
    return function() {
        self.apply(context, arguments);
    }
}

So basically it's returning another function which will call itself with the given context and arguments. If you then bind it again, you'll be binding this newly created function, which will be as if bind was implemented like:

 function bind(context) {
    var self = this;
    return function() {
        self.apply(context, arguments);
    }.bind(otherContext);
}

But because the inner function returned by bind acts as a closure where the original context is the one binded first (self), that one will be the context in with your function will be really executed.

Win32
  • 1,109
  • 10
  • 12
25

I thought it would be useful to illustrate Win32's answer with a picture.

A wrapper generated by bind makes sure your function is called with given context no matter what.
Such wrapper will always ignore its own context.

Given a chain of wrappers, any context but the innermost is lost.
Therefore, there is no way to change the context once it has been set using bind.

Chained bind calls

Community
  • 1
  • 1
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
3

This would actually solve you issue

const bind = Function.prototype.bind;
Object.defineProperty(Function.prototype, 'bind', {
    value: function () {
        const result = bind.apply(this, arguments);
        result.source = (this.source || this);
        return result;
    }
});

Now you can get the source property to get the original function. This could cause other issues, but performance does not seem to be one of them, https://jsperf.com/bind-override/1

Both IE, Edge, Firefox and Chrome seems to get the same result, sometimes the normal version is faster and sometimes the overridden is faster.

Peter
  • 37,042
  • 39
  • 142
  • 198
  • Would be nice to know why this was downvoted so i can improve the answer! – Peter Mar 13 '19 at 14:40
  • 2
    My guess is that you're getting downvoted because this changes the default behaviour of the `Function` prototype, which is generally frowned upon. I like your solution and I'm going to use this in my code. :) – Mr. X Jun 20 '19 at 09:20
  • Also, I'd recommend changing the line `result.source = this` to `result.source = (this.source || this)`, so that the source will remain the same no matter how many times a function is bound. – Mr. X Jun 20 '19 at 09:41
  • 2
    @Mr.X Great idea, i have made the changes you suggested. – Peter Jun 20 '19 at 10:44