I need to combine the power of JavaScript's call()
and apply()
methods. The problem I am having is that call()
retains the proper reference to this
, but sends the argument array that I have as an array when I need it sent as function arguments. The apply()
method sends the arguments to the function just fine when using an array, but I don't know how to send it the proper reference to this
that the call()
method seems to naturally have access to.
Below is a simplified version of the code that I have, it probably looks pretty useless, but its a good way to get the point across:
// AN OBJECT THAT HOLDS SOME FUNCTIONS
var main = {};
main.the_number = 15;
main.some_function = function(arg1, arg2, arg3){
// WOULD VERY MUCH LIKE THIS TO PRINT '15' TO THE SCREEN
alert(this.the_number);
// DO SOME STUFF WITH THE ARGUMENTS
...
};
// THIS STORES FUNCTIONS FOR LATER.
// 'hub' has no direct knowledge of 'main'
var hub = {};
hub.methods = [];
hub.methods.push(main.some_function);
hub.do_methods = function(arguments_array){
for(var i=0; i<this.methods.length; i++){
// With this one, '15' is printed just fine, but an array holding 'i' is
// just passed instead if 'i' itself
this.methods[i].call(arguments_array);
// With this one, 'i' is passed as a function argument, but now the
// 'this' reference to main is lost when calling the function
this.methods[i].apply(--need a reference to 'main' here--, arguments_array);
}
}
I need to combine the power of JavaScript's call()
and apply()
methods. The problem I am having is that call()
retains the proper reference to this
, but sends the argument array that I have as an array when I need it sent as function arguments. The apply()
method sends the arguments to the function just fine when using an array, but I don't know how to send it the proper reference to this
that the call()
method seems to naturally have access to.
Below is a simplified version of the code that I have, it probably looks pretty useless, but its a good way to get the point across:
// AN OBJECT THAT HOLDS SOME FUNCTIONS
var main = {};
main.the_number = 15;
main.some_function = function(arg1, arg2, arg3){
// WOULD VERY MUCH LIKE THIS TO PRINT '15' TO THE SCREEN
alert(this.the_number);
// DO SOME STUFF WITH THE ARGUMENTS
...
};
// THIS STORES FUNCTIONS FOR LATER.
// 'hub' has no direct knowledge of 'main'
var hub = {};
hub.methods = [];
hub.methods.push(main.some_function);
hub.do_methods = function(arguments_array){
for(var i=0; i<this.methods.length; i++){
// With this one, '15' is printed just fine, but an array holding 'i' is
// just passed instead if 'i' itself
this.methods[i].call(arguments_array);
// With this one, 'i' is passed as a function argument, but now the
// 'this' reference to main is lost when calling the function
this.methods[i].apply(--need a reference to 'main' here--, arguments_array);
}
}
Share
Improve this question
edited Jun 23, 2015 at 13:24
Randika Vishman
8,1243 gold badges61 silver badges82 bronze badges
asked May 22, 2012 at 18:49
Chris DutrowChris Dutrow
50.4k67 gold badges195 silver badges262 bronze badges
2
|
6 Answers
Reset to default 9What? Apply passes the scope as well...
method.apply(this, [args]);
Edit:
In your code you have the main object defined in the containing scope so you can simply do;
this.methods[i].call(main);
or
this.methods[i].apply(main, [args]);
When using apply
and call
the 1st parameter is what you want this
to be set to.
call
takes a list of arguments:
.call(main, i, j)
and apply
takes an array or arguments:
.apply(main, [i, j])
So, on this line:
this.methods[i].call([i]);
This passes [i]
as this
inside this.methods[i]
.
You probably want to do:
this.methods[i].call(main, i);
This will call this.methods[i]
, set this
to main
, and pass i
to it.
Or:
this.methods[i].call(main, arguments_array);
This will call this.methods[i]
, set this
to main
, and pass the elements of arguments_array
as parameters.
Just attach a reference to the parent object to the function object:
main.some_function = function () {
//...
}
main.some_function.parent = main;
// now some_function.parent holds a ref to main
Or, if you like, closures to the rescue: include a reference to main
when you define main.some_function
:
// self-execution function the returns a function with `that` (i.e., main) in scope
main.some_function = (function(that) {
return function() {
alert(that.the_number);
}
})(main);
As I read the comments in the preceding answers, I think, that you need some kind of binding. You can either use jQuery or similar library, or implement it yourself:
var bound_some_function = function(){
return main.some_function.apply(main, arguments);
}
// ...
hub.methods.push(bound_some_function);
// ...
this.methods[i].apply(null /*whathever, has no effect*/, arguments_array);
Universal definition of a bind function looks like this:
function bind(fun, scope){
return function(){
return fun.apply(scope, arguments);
};
}
For further reading about binding, google "Javascript function binding". There are lots of articles about it.
I had incorrect information before (in my deleted answer), since the apply will not have effects in a binded function (as the answer above);
Just adding a easy code to prove:
/*Lets suppose we have a class "MessageReader" that has a method printMessage
* that for some reason needs to receive the message to be printed from a function,
* not directly a value.*/
function MessageReader() {
this.value = "This is NOT a message and is not supposed to be readed!";
}
/*getMessage needs to return a string that will be printed!*/
MessageReader.prototype.printMessage = function(getMessage) {
console.log(getMessage.apply(this, []));
}
function Message(value) {
this.value = value;
}
Message.prototype.getMessage = function() {
return this.value;
}
var myMessageReader = new MessageReader();
var myMessage1 = new Message("This is a simple message");
var myMessage2 = new Message("This is another simple message");
/*This will print the wrong message:*/
myMessageReader.printMessage(myMessage1.getMessage);
/*But this works!*/
myMessageReader.printMessage(myMessage2.getMessage.bind(myMessage2));
The .call()
and .apply()
methods do not differ in terms of their handling with this
. From the MDN documentation:
While the syntax of this function is almost identical to that of apply(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
Unfortunately, calling a function with call / bind / apply and maintaining it's original this
reference isn't possible, however, as ugly is this may seem, there is a workaround where you can pass
function applyOriginalThis(fn, parametersArray)
{
var p = parametersArray;
switch (p.length)
{
case 0: fn(); break;
case 1: fn(p[0]); break;
case 2: fn(p[0], p[1]); break;
case 3: fn(p[0], p[1], p[2]); break;
case 4: fn(p[0], p[1], p[2], p[3]); break;
case 5: fn(p[0], p[1], p[2], p[3], p[4]); break;
case 6: fn(p[0], p[1], p[2], p[3], p[4], p[5]); break;
case 7: fn(p[0], p[1], p[2], p[3], p[4], p[5], p[6]); break;
case 8: fn(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]); break;
case 9: fn(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8]); break;
default: throw "Too many parameters.";
}
}
Of course, this will only work for the number of parameters that you wish to support. On the bright side, functions that take an excessive number of parameters is a code smell. Also, if I'm not mistaken, this type of code pattern was used in AngularJS 1.x as a performance optimization on .apply()
(can't find a reference right now).
this
to whatever you pass it... if you wantthis == main
then just passmain
as the first argument. – Snuffleupagus Commented May 22, 2012 at 18:54