使用`apply()`传递数组参数的同时保持`call()`中的`this`引用

8
我需要结合 JavaScript 的 call()apply() 方法的力量。我遇到的问题是,call() 保留了对 this 的正确引用,但将参数数组作为数组发送,而我需要将它作为函数参数发送。使用数组时,apply() 方法可以很好地将参数发送到函数中,但我不知道如何向其发送 call() 方法似乎自然具有的对 this 的正确引用。
下面是我拥有的代码的简化版本,它可能看起来没什么用,但这是一个很好说明问题的方法:
// 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); 
    }
}

apply 的第一个参数会将 this 设置为你传递的任何内容... 如果你想让 this == main,那么只需将 main 作为第一个参数传递即可。 - Snuffleupagus
@Snuffleupagus - 很遗憾,“hub”没有“main”的直接了解。 - Chris Dutrow
6个回答

9

什么?apply方法同样传递作用域...

method.apply(this, [args]);

编辑:

在你的代码中,你已经在包含范围中定义了主对象,因此你可以简单地这样做;

this.methods[i].call(main);

或者

this.methods[i].apply(main, [args]);

我可能漏掉了什么,但在这段代码的上下文中,我不知道如何获取到需要传递给“apply”的'this'。 - Chris Dutrow
我非常确定,如果我按照你的答案编写代码,它将传递对“hub”的引用而不是“main”。 - Chris Dutrow
很遗憾,“hub”没有关于“main”的直接知识。 - Chris Dutrow
1
调用没有访问权限。被用作“this”的对象是您传递给它的任何内容。在您的情况下,它是一个数组。 - Jivings
1
没问题,很高兴能帮忙!你需要做的就是将 main 保存在 hub 中。然后你就可以将它传回函数中了。 - Jivings
显示剩余3条评论

2

在使用applycall时,第一个参数是你想要设置为this的内容。

call接受一组参数:

.call(main, i, j)

apply函数接受一个参数数组:

.apply(main, [i, j])

因此,在这一行上:

this.methods[i].call([i]); 

这会将 [i] 作为 this 传递给 this.methods[i] 内部。
你可能想要执行如下操作:
this.methods[i].call(main, i);

这将调用this.methods[i],将this设置为main,并将i传递给它。

或者:

this.methods[i].call(main, arguments_array);

这将调用this.methods[i],将this设置为main,并将arguments_array的元素作为参数传递。

在上面的代码环境中,我如何获取'main'以便将其传递给apply?(假设'hub'没有直接了解'main')? - Chris Dutrow
1
@ChrisDutrow:hub需要对main有“直接了解”。它无法“猜测”你想使用main的意图。 - gen_Eric
非常抱歉,我的示例有点弱。我无法更改参数传递的方式,它们必须作为数组传递,并由函数解释为正常的参数。我已相应地更改了示例。 - Chris Dutrow
1
@ChrisDutrow:那么你应该使用.apply而不是.call。无论哪种方式,hub都需要知道关于main的信息才能正常工作。 - gen_Eric

1
只需将父对象的引用附加到函数对象即可:

main.some_function = function () {
    //...
}
main.some_function.parent = main;

// now some_function.parent holds a ref to main

或者,如果你愿意,可以使用闭包来解决问题:在定义 main.some_function 时,包含对 main 的引用。
// 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);

1

当我阅读之前的回答中的评论时,我认为您需要某种绑定。您可以使用jQuery或类似的库,也可以自己实现:

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);

绑定函数的通用定义如下:

function bind(fun, scope){
    return function(){
        return fun.apply(scope, arguments);
    };
}

如果想进一步了解绑定相关的内容,可以搜索“JavaScript函数绑定”。有很多关于这方面的文章。


0

.call().apply()方法在处理this方面没有区别。根据MDN文档:

虽然这个函数的语法与apply()几乎相同,但基本区别在于call()接受参数列表,而apply()接受单个参数数组。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call

不幸的是,使用call/bind/apply调用函数并保持其原始this引用是不可能的。然而,尽管这看起来很丑陋,但有一个解决方法,您可以传递

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.";
    }
}

当然,这只适用于您希望支持的参数数量。好的一面是,需要大量参数的函数是一种代码异味。此外,如果我没有记错,AngularJS 1.x中使用了这种类型的代码模式作为.apply()的性能优化(现在找不到参考资料)。


0

之前我的信息不正确(在我删除的答案中),因为 apply 在绑定函数中没有效果(就像上面的答案一样);

只需添加一个简单的代码来证明:

/*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));

1
如果您之前的回答是一个答案,那么您应该编辑它并更新信息,而不是删除它。在这个新的答案中,您对已删除答案的引用将无助于任何人,因为他们无法看到它... 它已经被删除了,对吧? - Mogsdad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接