Skip to content

Instantly share code, notes, and snippets.

@jacomyal
Created December 12, 2014 10:51
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • 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.

@DavidBruant
Copy link

I do not know who is the original author

Possibly John-David Dalton?

I do not know if there is an existing tests suit for polyfills

Standards test suites ;-)
https://github.com/tc39/test262/
https://github.com/tc39/test262/tree/master/test/built-ins/Function/prototype/bind
(may be hard to repackage to test polyfills, I don't know)

I do not know where to submit that fix if is the good way to solve my problem

MDN is a wiki ;-) I need to understand the problem first. I'll come back to that.

@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