How to determine if a function was called with `.call` or` .apply`?

I created this function to combine the methods:

export default function bindable(fn) {
    return function boundFn(...args) {
        return this === undefined ? fn(...args) : fn(this, ...args);
    }
}

      

It works with the binding operator. For example, you can define a new function like

 export const flatten = bindable(arrayOfArrays => Array.prototype.concat(...arrayOfArrays));

      

And then use it like this:

[[1,2,3],[4,5,6]]::flatten()

      

Or like this:

flatten([[1,2,3],[4,5,6]])

      

Babel REPL

This worked great until I realized that if I use it like in the second scenario, but import the helper method as a module, it breaks the context!

 import * as Arr from './array-helper-methods';
 Arr.flatten([1,2,3]); // `this` is now `Arr`

      

So my question is, is there a way that I can reliably determine if a function has been called using a bind statement?

+3


source to share


3 answers


This is a big and yet unsolved problem with the binding operator. But no, it only works for methods. You just have to provide two versions of your functions if you want to support both styles.

It is difficult to do this dynamically. No, you cannot tell if a function was called with ()

, as a method, was associated with call

or apply

, etc. And you really shouldn't be able to anyway.

In your case, I would choose



function bindable(fn, isContext) {
    return function boundFn(...args) {
        return isContext(this) ? fn(this, ...args) : fn(...args);
    }
}
export default bindable(bindable, f => typeof f == "function");

      

Then you can use bindable(flatten, Array.isArray)

or flatten::bindable(Array.isArray)

for flatten

. For more complex cases, you can also include arity function, ie fn.length + 1 == args.length

.

+2


source


This is more of a workaround than a real solution, but you can check if the this

property contains flatten

:



export default function bindable(fn) {
    return function boundFn(...args) {
        return (this === undefined || 'flatten' in this) ? fn(...args) : fn(this, ...args);
    }
}

      

+1


source


A more general (but not very good performance) solution would be to check if any method is a this

function boundFn

:

export default function bindable(fn) {
    return function boundFn(...args) {
        return (this == null || Object.values(this).some(x => x === boundFn))
            ? fn(...args)
            : fn(this, ...args);
    }
}

      

+1


source







All Articles