最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Javascript pass array of arguments using `apply()`, but keep `this` reference from `call()` - Stack Overflow

programmeradmin1浏览0评论

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
  • The first argument of apply sets this to whatever you pass it... if you want this == main then just pass main as the first argument. – Snuffleupagus Commented May 22, 2012 at 18:54
  • @Snuffleupagus - Unfortunately, 'hub' has no direct knowledge of 'main' – Chris Dutrow Commented May 22, 2012 at 19:04
Add a comment  | 

6 Answers 6

Reset to default 9

What? 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).

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论