26

I was reading the function definition of bind, but I can't 100% understand the code as written:

if (!Function.prototype.bind) {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function() {},
            fBound = function() {
                return fToBind.apply(this instanceof fNOP
                                       ? this 
                                       : oThis || window,
                                     aArgs.concat(Array.prototype.slice.call(arguments)));
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}

Specifically, I don't get the purpose of fNOP, and I don't understand why fBound's prototype needs to be set. I am also hung up at the fToBind.apply part (I can't figure out what this represents in this context).

Can someone can explain what is going on here?

Davis Dimitriov
  • 4,159
  • 3
  • 31
  • 45
  • 3
    I need more Vodka "Smirnoff" to answer this question... – NoWar Jan 10 '12 at 19:55
  • About halfway down on http://stage.learn.jquery.com/javascript-101/closures/, does that shed any insight? – j08691 Jan 10 '12 at 20:41
  • 2
    I don't think it will help much, but for reference: [`Function.prototype.bind` specification](http://ecma262-5.com/ELS5_HTML.htm#Section_15.3.4.5). – Felix Kling Jan 10 '12 at 20:52
  • @Davis It is NOT the definition of bind, it is a partial(!) workaround for older browsers. – user123444555621 Jan 11 '12 at 19:27
  • Keep in mind that MDN is a wiki, and may be erroneous. This is where the code came from: https://developer.mozilla.org/index.php?title=en/JavaScript/Reference/Global_Objects/Function/bind&action=diff&revision=31&diff=32 – user123444555621 Jan 11 '12 at 22:29
  • And here is a better implementation, with comprehensive comments: https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js – user123444555621 Jan 25 '12 at 12:57

4 Answers4

6

Well, one reason fBound's prototype needs to be set is so that the result of calling bind on a function has the same prototype as that function. This is also where fNop seems to come in--it lets you set fBound's prototype using new fNop() without calling the original function which may have side effects.

The call to apply lets you both set this in the function and specify additional arguments. Since bind lets you "curry" arguments to the function, you have to combine both the arguments passed in when the function is bound and the arguments it is called with.

Tikhon Jelvis
  • 67,485
  • 18
  • 177
  • 214
  • 3
    instead of using fNop, why can't you just say fBound.prototype = this.prototype? I believe it has to do with why the instanceof fNop check is made, and that is the part I cannot reason through – Davis Dimitriov Jan 10 '12 at 21:03
  • 1
    The point is, `bind` *creates a function*. So it's defined just to be consistent with *declaring a function*, where a new object is assigned to that function's `prototype` property. I guess we could use `fBound.prototype = Object.create(this.prototype)` but that wouldn't make much sense, since `Object.create` is likely not to exist in browsers that don't know about `bind`. – user123444555621 Jan 11 '12 at 20:39
  • "the result of calling bind on a function has the same prototype as that function." Actually, it's supposed to have no prototype at all, as [defined in ES5](http://es5.github.com/#x15.3.4.5) – user123444555621 Jan 19 '12 at 17:33
1

It is to make sure

  • (1) the bound function can be used as a constructor, ignoring the binding. (hence the instanceof check)
  • (2) At the same time, you want to make sure that new g() inherits from f's prototype chain. (hence the .prototype = new fNop part)

Example:

function f() {
    this.foo = 'bar';
}
f.prototype = {
    baz: 'yay!'
};

var g = f.bind({});
var o = new g();
console.log(o.foo); // 'bar' - (1)
console.log(o.baz); // 'yay!' - (2)

At the moment you call new g(), the fBound function is called as a constuctor with a brand new object object (this) that is an instance of fNop.


Edit:

The ECMAScript5 standard defines a complicated algorithm for binding functions. Amongst others, the following assertions must hold true:

var DateJan2042 = Date.bind(null, 2042, 0);

 /*1*/ console.assert(Function.prototype.bind.length == 1, 'bind should have a length of 1');
 /*2*/ console.assert(typeof DateJan2042 == 'function', 'bind() should return a function');
 /*3*/ console.assert(!DateJan2042.hasOwnProperty('prototype'), 'Bound function must not have a prototype');
 /*4*/ console.assert(DateJan2042.length == Math.max(Date.length - 2, 0), 'Bound function should have a proper length');
 /*5*/ console.assert(typeof DateJan2042() == 'string', 'Function call should return a string');
 /*6*/ console.assert({}.toString.call(new DateJan2042()).indexOf('Date') != -1, 'Constructor call should return a new Date object');
 /*7*/ console.assert(new DateJan2042() instanceof DateJan2042, 'Instanceof check should pass for constructor\'s return value');
 /*8*/ console.assert((new DateJan2042()).getMonth() == 0, 'Constructor should be called with bound arguments');
 /*9*/ console.assert((new DateJan2042(1)).getDate() == 1, 'Constructor should take additional arguments');
/*10*/ console.assert(!/^function *\( *[^ )]/.test(Function.prototype.toString.call(DateJan2042)), 'Bound function should have no formal arguments');

Since a properly bound function is not a real Function object, it's impossible to get it all right using a polyfill (Particularly numbers 2/3, and 4/10), but you can try to implement as much as possible.

The implementation in question tries to solve number 6 and number 7, by hooking into the prototype chain, but that's not enough.

Here is an alternative implementation that works a bit better, but is still not perfect: http://jsfiddle.net/YR6MJ/

user123444555621
  • 148,182
  • 27
  • 114
  • 126
0
// check to see if the native implementation of bind already
// exists in this version of JavaScript. We only define the
// polyfill if it doesn't yet exist.

if (!Function.prototype.bind) {

  // creating the bind function for all Function instances by 
  // assigning it to the `Function.prototype` object. Normally 
  // you would avoid assigning to builtin prototypes because you
  // may cause a conflict with new features, but here this is a
  // known feature that is already in the spec that we're adding
  // to a JavaScript runtime that is not up to spec, so its ok

  Function.prototype.bind = function (oThis) {

    // if you attempt to call this function from a non-function object
    // for example if you assign this bind function to a normal object
    // or use `call`/`apply` to change the context of this function call to
    // a non function value (e.g. `Function.prototype.bind.call({})`), we
    // throw an error because bind can only work on functions, and we
    // require that `this` in this call is a function

    if (typeof this !== "function") {
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    // bind does two things, it binds a context (`this` value) to a
    // function for when its called, and it provides a way to bake in
    // some pre-defined arguments that are automatically passed into 
    // that function when called. Those arguments can be passed into
    // the bind call and get picked up here as `aArgs` pulling them
    // from `arguments` making sure to lop off the `oThis` value

    var aArgs = Array.prototype.slice.call(arguments, 1),

      fToBind = this, // this is the function we're binding
      fNOP = function () {}, // a constructor used for `new` usage (see below)

      // `fBound` is the bound function - the result that bind is going to
      // return to represent the current function (`this` or `fToBind`) with
      // a new context.  The idea behind this function is that it will simply
      // take the original function and call it through `apply` with the
      // new context specified.

      fBound = function () {

        // call the original function with a new context using `apply`.
        // however if the function is called with `new`, it needs to be called
        // with the context of, and return, a new object instance and not the
        // bound version of this.  In that case, binding gets ignored in favor
        // of using the `this` of the new instance rather than the `oThis` binding.

        // new object instances inherit from the prototype of their constructors.
        // Our `fBound` function is supposed to mimic the original with the
        // exception of a change in context.  So if new objects are created with
        // it, they should behave as though they were created from the original.
        // But at the same time, we can't simply carry over the prototype of the
        // original into `fBound` because it is a separate function and needs its
        // own prototype, just one that also inherits from the original. To
        // accommodate this, the `fNOP` function (constructor) above is used as
        // an intermediary for creating `fBound`'s prototype while allowing it to
        // be unique but also inherit the original.  And because that becomes part
        // of the bound function's prototype chain, it can be used to determine
        // whether `this` in `fBound` is an instance created by `new` or not since
        // `instanceof` works through a prototype chain lookup.

        return fToBind.apply(this instanceof fNOP
               ? this
               : oThis,

               // call the function with arguments that include the added 
               // arguments specified from the original bind call plus
               // the arguments this function was called with

               aArgs.concat(Array.prototype.slice.call(arguments)));
      };

    // `fNOP`'s use to provide an intermediary prototype between `fBound` and
    // the current function instance mimics `Object.create`. But we're assuming
    // if you don't have `bind`, you probably don't have `create` either, so do
    // it the old fashioned way with a constructor.  This works by setting the
    // constructor's prototype to the to-inherit-from constructor's (this)
    // prototype. A check is needed to prevent assinging that prototype to null
    // if it doesn't exist on this function (Function.prototype is technically
    // a valid target for `bind()` because it is a function but one that does not
    // have its own prototype).

    if (this.prototype) {
      fNOP.prototype = this.prototype;
    }

    // here the inheritance is made.  As a new function, `fBound` has no existing
    // inheritance chain to worry about, so we can easily replace it with a new
    // one - that of a new instance `fNOP`.  Since `fNOP`'s prototype is the original
    // function's prototype, `fBound` has a prototype which directly inherits from
    // that, one level between new instances and the original prototype. So
    // `fBound.prototype.__proto__ === this.prototype` and new instances of `fBound`
    // created with `new fBound()` will inherit from `fBound.prototype` as well as
    // the original function's prototype.

    fBound.prototype = new fNOP();

    // return the bound version of the function as
    // the result of the bind call

    return fBound;
  };
}
Ankur Anand
  • 3,873
  • 2
  • 23
  • 44
0

From an earlier comment:

instead of using fNop, why can't you just say fBound.prototype = this.prototype?

As far as I can tell, the primary difference is that when the value of this inside the bound function is an instance of the original function on which bind was invoked, then the value to bind to -- the first argument originally passed to bind -- is ignored.

For example, this code:

function Test(blah) {
    console.log(this.length, blah);
}

Test.prototype.length = 77;
Test.prototype.fn = Test.bind(['a', 'b', 'c'], "testing");
new Test().fn()

...causes fn to print:

77 testing

In other words, the value of this inside fn is the Test instance on which it was invoked. Your suggestion would supply the bound array to the apply inside bind, so, written that way, the last line of the same code would print:

3 testing

It's not completely clear to me why this is important, but it does highlight that your suggestion would not produce equivalent results.

Wayne
  • 59,728
  • 15
  • 131
  • 126
  • That behaviour as a whole seems *not* to comply with the standards. Once bound, `this` cannot be overriden (no matter who the invoker is, `instanceof` the origin or not). So there should be no case where `oThis` isn't used (making the condition at best unnecessary and at worst non-standard). – davin Jan 10 '12 at 22:10