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();
}
...
},
//....
1 Answer 1
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.
-
\$\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\$Julien CROUZET– Julien CROUZET2014年08月14日 09:22:01 +00:00Commented 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 calledbind
). jQuery is the odd one, since it calls itproxy
, because older versions of jQ already usedbind
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– Flambino2014年08月14日 10:47:35 +00:00Commented Aug 14, 2014 at 10:47
bind
(developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…)? \$\endgroup\$