Skip to content

Instantly share code, notes, and snippets.

@jacomyal
Created December 12, 2014 10:51
Show Gist options
  • Save jacomyal/4b7ae101a1cf6b985c60 to your computer and use it in GitHub Desktop.
Save jacomyal/4b7ae101a1cf6b985c60 to your computer and use it in GitHub Desktop.
A problem with Function.prototype.bind polyfill

I met an issue with the common polyfill for Function.prototype.bind, found on the MDN, and I do not know where to fix it.

Here is the polyfill:

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 && oThis
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

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

    return fBound;
  };
}

Context

I was trying to unit test some React components usign a Mocha / PhantomJS wrapper for Gulp. This gulp task uses PhantomJS v1.9, that does not have Function.prototype.bind support (source). And Function.prototype.bind is used some times in React source code, so I used the polyfill.

The issue

When I ran my script, I received the following error:

TypeError: instanceof called on an object with an invalid prototype property.

After some investigations, I found that there is a specific call of bind from React that made the polyfill crash (link):

var HTMLDOMPropertyConfig = {
  isCustomAttribute: RegExp.prototype.test.bind(
    /^(data|aria)-[a-z_][a-z\d_.\-]*$/
  )
  // [...]
};

RegExp.prototype.test is a native function and has no prototype, so the line fNOP.prototype = this.prototype; was setting fNOP's prototype to undefined, which made the test this instanceof fNOP throw the error: the right operand of instanceof must have an object prototype.

After having discussed with @rauschma on Twitter, he told me that this while fNOP's related code is certainly here to deal with using a bound function as a constructor, which makes sense.

The solution

I think that just testing that the function to bind has actually a prototype before setting it as fNOP.prototype would solve the issue. So the fixed version would be:

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 && oThis
                 ? this
                 : oThis,
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    // test this.prototype in case of native functions binding:
    if (this.prototype)
      fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

My problem is that:

  1. I do not know who is the original author
  2. I do not know if there is an existing tests suit for polyfills
  3. I do not know where to submit that fix if is the good way to solve my problem

A quick Github search shows that this polyfill is used in many places, and if it fails with binding native functions, it looks a bit dangerous to let that out.

@jareguo
Copy link

jareguo commented Nov 5, 2015

Thanks 👍

@ohmyhusky
Copy link

Still can not understand why should we check '&& oThis', is it means if we pass a null to bind like s = f.bind(null), then no matter use s as constructor -- new s,or normal function invoke -- s(), we always pass null to f.apply as f.apply(null,.....)? And then the null will be force change to global inside the function call f.apply. Why do me do this? Can you please help me out? thanks a lot in advance!

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