Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

If we polyfill fn.bind() in JavaScript, why do you have to check the type of "this"?

I see in the Mozilla polyfill of fn.bind() like this:

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');
    }

    // other code omitted here...

  };
}

I don't understand why we have to check the type of this... because if we say fn.bind() and fn is a function, then it will work, and if fn is not a function, then fn.bind will never reach Function.prototype.bind by prototypal inheritance. So why do we have to check the type of this?

like image 306
nonopolarity Avatar asked Jan 19 '16 01:01

nonopolarity


2 Answers

if fn is not a function, then fn.bind will never reach Function.prototype.bind by prototypal inheritance.

True, however this is not the only way this could be set. For example, if we were to use the .call or .apply methods on the bind function itself, or do something really crazy like assign it to other objects, it would behave differently from the native function.

Considering the following code using the native ES5 method:

var notAFunction = {};
var someObject = {};

Function.prototype.bind.call(notAFunction, someObject);

This will throw a TypeError, like the following:

TypeError: Function.prototype.bind called on incompatible Object

This extra check is basically emulating this sanity check from the native function as closely as it can.


So why do we have to check the type of this?

You don't technically have to, as this case would be an edge-case for most sane code, but in order to make the polyfill behave more-closely to the ES5 spec, it is nice. Since ES5 browsers will never even run this code, there is also no performance hit for modern browser.


This behavior is defined in the ECMA-262 5.1 Edition specification and onward:

  1. Let Target be the this value.
  2. If IsCallable(Target) is false, throw a TypeError exception.
like image 152
Alexander O'Mara Avatar answered Nov 17 '22 10:11

Alexander O'Mara


Not all callable objects inherit from (or are equal to) Function.prototype. For example:

typeof document.createElement('object') === "function"; // true
document.createElement('object') instanceof Function; // false

And even objects which inherit from Function.prototype could have bind shadowed:

var f = function() {};
f.bind = 123;
f.bind === Function.prototype.bind; // false

However, you may want to be able to call Function.prototype.bind on those callable objects. That's why we have things like Function.prototype.call, Function.prototype.apply or Reflect.apply. You can use those to call bind on any object, whether it's callable or not.

The inverse is also possible. You can have an object which inherits from Function.prototype and is not callable. For example:

Object.create(Function.prototype) instanceof Function; // true
typeof Object.create(Function.prototype) === "function"; // false

Therefore, you can't make any assumption. If you want the polyfill to conform to ES5, you must check whether the object is callable. In ES5, checking whether typeof returns "function" does exactly that (but in ES3 it might not work for host objects).

like image 22
Oriol Avatar answered Nov 17 '22 10:11

Oriol