31

Is there a way to determine if a JavaScript function is a bound function?

Example:

var obj = {
  x:1  
};

function printX() {
    document.write(this.x);
}

function takesACallback(cb) {
  // how can one determine if this is a bounded function
  // not just a function?
  if (typeof cb === 'function') {
    cb();  
  }
}

takesACallback(printX.bind(obj)); // 1
takesACallback(printX);           // undefined

Perhaps this is an important point. I am not asking why the second call prints undefined.

New Alexandria
  • 6,951
  • 4
  • 57
  • 77
robbmj
  • 16,085
  • 8
  • 38
  • 63
  • 1
    Every JS function is bound to its scope, so what is the point here? What do you want to do with this information? – crackmigg Feb 28 '16 at 18:58
  • It's nothing that I want it to do, I am just curious if there is a way to do it. – robbmj Feb 28 '16 at 18:59
  • 1
    @Kyll: You still should not do that (too much magic). And it doesn't make a difference anyway, just always use `call` and when the function is bound you don't need to care – Bergi Mar 07 '16 at 15:59
  • 1
    @Kyll: That doesn't sound useful at all to me. It's not the responsibility of the caller to check what the callback is going to do with the passed values (APIs), and even a mere warning would at least end up with a large number of false positives. – Bergi Mar 07 '16 at 16:10

5 Answers5

38

Both bound functions and arrow functions do not have a prototype property:

typeof (function() {}).prototype // 'object' as usual
typeof (function() {}).bind(null).prototype // 'undefined'!
typeof (() => {}).prototype // 'undefined'!

This is not 100% safe since you could still manually assign this property (although that'd be weird).
As such, a simple way to check for bindability would be the following:

// ES5
function isBindable(func) {
  return func.hasOwnProperty('prototype');
}

// ES6
const isBindable = func => func.hasOwnProperty('prototype');

Usage:

isBindable(function () {}); // true
isBindable(() => {}); // false
isBindable(
  (function () {}).bind(null)
); // false

This way you can make sure that the function that has been passed can deal with a dynamic this.

Here is an example usage for which the above fails:

const arrowFunc = () => {};
arrowFunc.prototype = 42;

isBindable(arrowFunc); // true :(

Interestingly, while bound functions do not have a prototype property they can still be used as constructors (with new):

var Animal = function(name) {
   this.name = name;
};

Animal.prototype.getName = function() {
  return this.name;
};

var squirrel = new Animal('squirrel');
console.log(squirrel.getName()); // prints "squirrel"

var MutatedAnimal = Animal.bind({}); // Radiation :)
console.log(MutatedAnimal.hasOwnProperty('prototype')); // prints "false"

var mutatedSquirrel = new MutatedAnimal('squirrel with two heads');
console.log(mutatedSquirrel.getName()); // prints "squirrel with two heads"

In that case, the original function prototype (Animal) is used instead.
See JS Bin, code and link courtesy of Dmitri Pavlutin.

This of course won't work with arrow functions since they can't be used as constructors.

Unfortunately, I don't know if there is a way to distinguish a bound function (usable as constructor) from an arrow function (not usable as constructor) without trying them out with new and checking if it throws (new (() => {}) throws a "is not a constructor" error).

Community
  • 1
  • 1
Kyll
  • 7,036
  • 7
  • 41
  • 64
  • "*This way you can make sure that the function that has been passed can deal with a dynamic this*" - every function can cope with a dynamic `this` value. Some (like bound functions) just won't use the one you are passing. – Bergi Mar 07 '16 at 16:01
  • Well, it can't identify the old stubborn `function() { method.apply(self, arguments) }` which doesn't take a different `this` either but is detected to be another "kind of function" than `method.bind(self)`. It doesn't make sense to distinguish them anywhere; just always provide the API. – Bergi Mar 07 '16 at 16:08
  • Could you give some reference on how did you find out about the `prototype` missing after `bind` function? Thanks. – Alan Dong Sep 28 '16 at 18:47
  • @Kyll, FYI, I did the experiment as well. Along with `prototype`, `caller` and `arguments` are also missing from binded function's own properties. See my blog [here](https://www.linkedin.com/pulse/javascript-function-binding-alan-dong?trk=prof-post). I just wanted to see if there's an official document that indicates this way is authentic. So far, I've found nothing. – Alan Dong Sep 28 '16 at 22:50
  • @AlanDong I'm not on that network sadly, so I can't see it. I'll dig into the spec when I have some time to see if there's an article about it. – Kyll Sep 29 '16 at 08:10
  • Really good to know. Thanks for the interesting insight. And for the *Zak McKracken and the Alien Mindbenders* reference – Kamafeather Jul 06 '18 at 15:24
19

In environments that support ES6, you can check whether the name of the function starts with "bound " (the word "bound" followed by a space).

From the spec:

19.2.3.2 Function.prototype.bind ( thisArg , ...args)

[...]

15. Perform SetFunctionName(F, targetName, "bound").

Of course that could result in false positives if the name of the function was manually changed.

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 4
    This is a better test than the accepted answer because native functions often also have an empty prototype. E.g. `[].slice.prototype` is undefined. – Lea Verou Aug 07 '17 at 08:47
  • 3
    Checking for both conditions is probably the safest way: `function isBound(func) { return func.name.startsWith('bound ') && !func.hasOwnProperty('prototype'); }` – Tey' Mar 16 '19 at 18:19
  • false positive `const b = { ["bound or maybe not"]: _=>_ }["bound or maybe not"]` – kigiri Apr 24 '20 at 22:51
5

One could override the existing prototype bind, tagging functions that have been bound.

A simple solution. This will likely kill certain optimizations in V8 (and possibly other runtimes) because of hidden classes, though.

(function (bind) {
  Object.defineProperties(Function.prototype, {
    'bind': {
      value: function (context) {
        var newf = bind.apply(this, arguments);
        newf.context = context;

        return newf;
      }
    },
    'isBound': {
      value: function () {
        return this.hasOwnProperty('context');
      }
    }
  });
}(Function.prototype.bind));

In motion:

(function (bind) {
  Object.defineProperties(Function.prototype, {
    'bind': {
      value: function (context) {
        var newf = bind.apply(this, arguments);
        newf.context = context;

        return newf;
      }
    },
    'isBound': {
      value: function () {
        return this.hasOwnProperty('context');
      }
    }
  });
}(Function.prototype.bind));

var a = function () {
  console.log(this);
};
var b = {
  b: true
};
var c = a.bind(b);

console.log(a.isBound())
console.log(c.isBound())
console.log(c.context === b);
a();
c();
Oka
  • 23,367
  • 6
  • 42
  • 53
  • I have done some tests and it does not seem to impact performance that much! https://jsperf.com/bind-override/1 – Peter Feb 08 '19 at 09:32
0

You would need to write your own bind function on the prototype. That function would build an index of what has been bound.

You could then have another function to perform a lookup against the object where that index is stored.

New Alexandria
  • 6,951
  • 4
  • 57
  • 77
  • This is true, I know how I could do it if I were to implement bind myself. Perhaps you could share an implementation for future readers. – robbmj Feb 28 '16 at 19:02
  • 1
    But this really is your only option because `printX()` is the same as `printX.bind(this)` with `this` being your current scope. – crackmigg Feb 28 '16 at 19:04
  • I need to read closely the top answer and decide if I will delete this altogether. – New Alexandria Mar 09 '16 at 01:57
  • Don't downvote this because this would be actually technically the only way to do that. Probably (= in overwhelming most cases) a bad idea though. – freeeman Jan 10 '22 at 13:33
0

Based on previous answers, I create a function to determine:

function isBoundFunction(func) {
    if(typeof func.prototype === 'object') return false
    try {
        new func()
    }
    catch(e) {
        return false
    }
    return true
}

This function determine three type of functions: 1. original function, whose prototype is object, 2. arrow function, which can not be used as constructor, 3. bound function.

frustigor
  • 392
  • 3
  • 8
  • 2
    Yep, although `new func()` can have significant side-effects (if object pooling is implemented for example, that's a memory leak). – Kyll Jun 16 '17 at 12:36