2012-05-22 14 views
5

Necesito combinar el poder de los métodos de call() y apply() de JavaScript. El problema que estoy teniendo es que call() conserva la referencia adecuada a this, pero envía el argumento array que tengo como una matriz cuando lo necesito enviado como argumentos de función. El método apply() envía los argumentos a la función muy bien cuando se utiliza una matriz, pero no sé cómo enviar la referencia adecuada al this que el método call() parece tener acceso naturalmente.Javascript pasa una matriz de argumentos utilizando `apply()`, pero mantenga `this` referencia de` call() `

A continuación se muestra una versión simplificada del código que tengo, es probable que se parece bastante inútil, pero es una buena manera de conseguir el punto a través de:

// 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); 
    } 
} 
+0

El primer argumento de apply establece 'this' en lo que sea que pase ... si quiere' this == main', simplemente pase 'main' como primer argumento. – Snuffleupagus

+0

@Snuffleupagus - Desafortunadamente, 'hub' no tiene conocimiento directo de 'main' –

Respuesta

7

¿Qué? Apply passes the scope as well ...

method.apply(this, [args]); 

Editar:

En su código que tiene el objetivo principal definido en el alcance que contiene por lo que simplemente puede hacer;

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

o

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

Me puede estar faltando algo, pero en el contexto de este código, no veo cómo llegar al 'esto' que necesito pasar Aplicar". –

+0

Es idéntico a cualquier forma que use el método de llamada ... – Jivings

+0

Estoy bastante seguro de que si hago el código en su respuesta, pasará una referencia a 'hub' y no 'principal' –

2

Al utilizar apply y call 1er parámetro es lo que quiere this que se fijará a.

call toma una lista de argumentos:

.call(main, i, j) 

y apply toma una matriz o argumentos:

.apply(main, [i, j]) 

Por lo tanto, en esta línea:

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

Esta pasa [i] como this dentro de this.methods[i].

es probable que desee hacer:

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

Para ello, será this.methods[i], establece this a main, y pasar i a ella.

O:

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

Para ello, será this.methods[i], establezca this a main, y pasar los elementos de arguments_array como parámetros.

+0

En el contexto del código anterior, ¿cómo obtengo 'principal' para poder pasarlo y aplicarlo? (¿Asumiendo que 'hub' no tiene conocimiento directo de 'main'? –

+1

@ChrisDutrow: 'hub' necesita tener" conocimiento directo "de' main'. No hay forma de que "descubra" que quieres usar ' main'. –

+0

Parece que 'call()' puede acceder a una referencia a 'main' de alguna manera ... –

1

Sólo hay que conectar una referencia al objeto padre al objeto función:

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

// now some_function.parent holds a ref to main 

O, si se quiere, cierres al rescate: incluir una referencia a main cuando se 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); 
1

Al leer los comentarios en las respuestas anteriores, creo que necesita algún tipo de enlace. También se puede usar jQuery o biblioteca similares, o implementar por sí mismo:

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

definición universal de una función de enlace se parece a esto:

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

para la lectura adicional acerca del enlace, Google "función Javascript vinculante" . Hay muchos artículos al respecto.

0

Tenía información incorrecta antes (en mi respuesta eliminada), ya que la aplicación no tendrá efectos en una función combinada (como la respuesta anterior);

Simplemente añadiendo un código fácil de demostrar:

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

Si su respuesta anterior fue una respuesta, entonces debería haberla editado con información actualizada en lugar de eliminarla. En esta nueva respuesta, sus referencias a una respuesta eliminada no ayudarán a nadie, porque no pueden ver es ... ha sido eliminado _, ¿derecho? – Mogsdad

0

Los métodos .call() y .apply() no difieren en cuanto a su manejo con this. De la documentación de MDN:

Si bien la sintaxis de esta función es casi idéntica a la de apply() .

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

Desafortunadamente, llamar a una función de llamada/bind/aplicar y mantener que es original, this referencia no es posible, sin embargo, tan feo es esto pueda parecer, hay una solución para que pueda pasarle

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

Por supuesto, esto solo funcionará para la cantidad de parámetros que desee. En el lado positivo, las funciones que toman un número excesivo de parámetros son code smell. Además, si no me equivoco, este tipo de patrón de código se utilizó en AngularJS 1.x como optimización del rendimiento en .apply() (no puedo encontrar una referencia en este momento).

Cuestiones relacionadas