4
\$\begingroup\$

What do you think of the following "utility" :

/**
 * Contextualize a method for callback
 *
 * @param    methodName      String Method name
 * @param    context         Object Context (this)
 * @param    mixed  OPTIONAL Argument to place at first
 * @param    mixed  OPTIONAL Argument to place at second (etc...)
 */
contextualize: function(methodName, context) {
    var otherArgs;
    if (
            (typeof context[methodName] === "undefined") ||
            !!!(context[methodName].constructor &&
                    context[methodName].call &&
                    context[methodName].apply)) {
        error("No " + methodName + " function defined");
    }
    otherArgs = $.makeArray(arguments).slice(2);
    return (function(obj, otherArgs) {
        return (function() {
            var args = [];
            $.merge(args, otherArgs);
            $.merge(args, $.makeArray(arguments));
            return (obj[methodName].apply(obj, args));
        });
    }(context, otherArgs));
},

For example, I use it to use the "fooBar" method of current object as callback of a click event, forcing a true as first argument :

$('#button').click(Utils.contextualize('fooBar', this, true));

That will use correctly the following method :

//....
fooBar: function(fromButton, event) {
    event.preventTarget();
    if (fromButton) {
      this.anotherMethod();
    }
    ...
},
//....
\$\endgroup\$
1

1 Answer 1

7
\$\begingroup\$

First of all, since you're using jQuery already, why not use $.proxy() which does the same thing?

$('#button').click($.proxy(this, 'fooBar', true));

That's equivalent to your contextualize example, as far as I can tell. And $.proxy has the added benefit of working with functions themselves, not just their names. That means you can use it to wrap a function from some other object in a given context. I.e.

$('#button').click($.proxy(this.fooBar, someOtherContext, true));

Meanwhile, your contextualize can't similarly "re-contextualize" a function; it only works for functions present in the context argument.

There's also the built-in Function.prototype.bind which will return a function bound to a given context, like $.proxy does above:

$('#button').click(this.fooBar.bind(this, true));

It's available in all modern browsers, but if you want more compatibility, use $.proxy.

Second: !!!(...). Just use 1 negation, please. It'll coerce a value to a boolean and negate it just fine; there's absolutely no reason to negate it 2 more times. Besides, since the expression in the parentheses is boolean logic, it's already is a boolean value to begin with, so it really is straight-up negation; no coercion involved. Yes, using !! can be a handy way of coercing a value to a boolean without negating it, but even that is almost never needed since whatever you're coercing is thruthy or falsy all on its own (if(!!something) is equivalent to just if(something))

I'd also skip the "is this a function" check entirely. JS will complain all by itself, if you let it. Of course, it won't call your error function when it complains, but I'd rather have JS complain loudly than go through a custom error function. Sure, your function checks for the function's existence right away, rather than failing when trying to call it, but you can always remove the function from the context object afterward, so the upfront check guarantees nothing.

var boundFunction = Utils.contextualize(contextObj, 'fooBar');
delete contextObj['fooBar']; // boundFunction will now fail when called

Or you might want to add the function after calling contextualize, but you can't do that right now without error being called.

Third: You're returning a function by invoking a function that returns a function. Much like the triple-negation above, that's an unnecessary, round-about way of doing things. This will do the same

return function() {
    var args = [];
    $.merge(args, otherArgs);
    $.merge(args, $.makeArray(arguments));
    return (context[methodName].apply(context, args));
};

otherArgs and context are already available as closures, so there's no reason to pass them to a function, just to create new closures there.

So, yeah, your function will work, but it's unnecessary and overly complex, while being less versatile than the alternatives you already have at hand. I wouldn't recommend using it, to be honest. Sorry.

\$\endgroup\$
2
  • \$\begingroup\$ No reason to be sorry ... thank you. Been using this function for years w/o knowing the existence of $.proxy, my bad. I wrote it for the exact reason of $.proxy ... I can now use $.proxy happily and thanks for all the infos \$\endgroup\$ Commented Aug 14, 2014 at 9:22
  • \$\begingroup\$ @JulienCROUZET No problem. As you can tell, context-binding is such a common thing that it's included in JavaScript itself as the bind method, but every major library also has a version it (usually also called bind). jQuery is the odd one, since it calls it proxy, because older versions of jQ already used bind for attaching event handlers. By the way, wait a while to see if someone else chimes in with an answer, otherwise click the checkmark. \$\endgroup\$
    – Flambino
    Commented Aug 14, 2014 at 10:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.