2008-11-26 13 views
52

Estoy tratando de escribir una función de JavaScript que devolverá su primer argumento (función) con todos los demás argumentos como parámetros preestablecidos para esa función.¿Cómo puedo preestablecer argumentos en la llamada a función de JavaScript? (Aplicación de función parcial)

Así:

function out(a, b) { 
    document.write(a + " " + b); 
} 

function setter(...) {...} 

setter(out, "hello")("world"); 
setter(out, "hello", "world")(); 

salida sería "hola mundo" dos veces. para alguna implementación de setter

Me encontré con un problema con la manipulación de la matriz de argumentos en mi primer intento, pero parece que habría una mejor manera de hacerlo.

+0

Recomiendo la función 'curry' de [Lodash] (http://lodash.com/docs#curry). Por cierto, recomiendo toda la biblioteca, es muy útil. –

Respuesta

96

En primer lugar, se necesita un parcial - there is a difference between a partial and a curry - y aquí es todo lo que necesita, sin un marco:

function partial(func /*, 0..n args */) { 
    var args = Array.prototype.slice.call(arguments, 1); 
    return function() { 
    var allArguments = args.concat(Array.prototype.slice.call(arguments)); 
    return func.apply(this, allArguments); 
    }; 
} 

Ahora, usando el ejemplo, se puede hacer exactamente lo que está buscando:

partial(out, "hello")("world"); 
partial(out, "hello", "world")(); 

// and here is my own extended example 
var sayHelloTo = partial(out, "Hello"); 
sayHelloTo("World"); 
sayHelloTo("Alex"); 

La función partial() podrían utilizarse para poner en práctica, pero no es currificación. Aquí es una cita de a blog post on the difference:

Cuando la aplicación parcial toma una función y de ella construye una función que toma menos argumentos, currificación construye funciones que tienen múltiples argumentos por la composición de funciones que cada uno tome un solo argumento.

Espero que ayude.

+0

¿Podría editar esto? func.apply (this, allArguments) debe devolverse func.apply (this, allArguments) – AlexH

+0

Listo - realmente no estaba buscando más allá de su función de ejemplo no recurrente, así que lo descuidé originalmente. Gracias. –

+0

Estoy votando tu respuesta solo porque he buscado algo para hacer esto antes. –

3

¿Es curried javascript lo que estás buscando?

+0

No creo que necesite currying, per se - ver http://stackoverflow.com/questions/218025/what-is-the-difference-between-currying-and-partial-application –

+1

El enlace ya no funciona. ¿Es esta la misma página? http://www.crockford.com/javascript/www_svendtofte_com/code/curried_javascript/index.html –

2

Si usa Dojo solo debe llamar a dojo.hitch() que hace casi exactamente lo que desea. Casi — porque se puede usar para empaquetar el contexto también. Sin embargo, su ejemplo es primero:

dojo.hitch(out, "hello")("world"); 
dojo.hitch(out, "hello", "world")(); 

Así como:

var A = { 
    sep: ", ", 
    out: function(a, b){ console.log(a + this.sep + b); } 
}; 

// using functions in context  
dojo.hitch(A, A.out, "hello")("world"); 
dojo.hitch(A, A.out, "hello", "world")(); 

// using names in context 
dojo.hitch(A, "out", "hello")("world"); 
dojo.hitch(A, "out", "hello", "world")(); 

dojo.hitch() es la parte de la Base de Dojo, por lo que tan pronto como se haya incluido dojo.js que está ahí para tú.

Otro recurso general está disponible en el módulo dojox.lang.functional.curry (documentado en Functional fun in JavaScript with Dojo — basta con mirar en esta página para ver "curry"). Específicamente, es posible que desee ver curry() y parcial().

curry() acumula argumentos (como en su ejemplo) pero con una diferencia: tan pronto como se cumple la arit, llama a la función que devuelve el valor. La implementación de su ejemplo:

df.curry(out)("hello")("world"); 
df.curry(out)("hello", "world"); 

en cuenta que la última línea no tiene "()" al final — se le llama de forma automática.

parcial() permite sustituir argumentos al azar:

df.partial(out, df.arg, "world")("hello"); 
0

** EDIT: Véase la respuesta de Jason Bunting. De hecho, esta respuesta muestra una forma sub-par de encadenar numerosas llamadas salientes, no una única llamada saliente con preajustes para algunos de los argumentos. Si esta respuesta realmente ayuda con un problema similar, debe asegurarse de utilizar apply y call como Jason recomienda, en lugar de la forma oscura de usar eval que pensé. **

Bueno ... su fuera realmente escribirá "indefinido" mucho en esto ... pero esto debe estar cerca de lo que quiere:

function out(a, b) { 
    document.write(a + " " + b); 
} 

function getArgString(args, start) { 
    var argStr = ""; 
    for(var i = start; i < args.length; i++) { 
     if(argStr != "") { 
      argStr = argStr + ", "; 
     } 
     argStr = argStr + "arguments[" + i + "]" 
    } 
    return argStr; 
} 

function setter(func) { 
    var argStr = getArgString(arguments, 1); 
    eval("func(" + argStr + ");"); 
    var newSettter = function() { 
     var argStr = getArgString(arguments, 0); 
     if(argStr == "") { 
      argStr = "func"; 
     } else { 
      argStr = "func, " + argStr; 
     } 
     return eval("setter(" + argStr + ");"); 
    } 
    return newSettter; 
} 

setter(out, "hello")("world"); 
setter(out, "hello", "world")(); 

probablemente me muevo el código en getArgString en la función setter en sí ... un poco más seguro ya que usé 'eval's.

+0

Idea correcta, pero implementación horrible. Vea mi respuesta: no necesita crear cosas como cadenas, JavaScript tiene funciones call() y apply() que facilitan cosas como esta. –

+0

Huh ... nunca antes había oído hablar de funciones de llamada o de solicitud; eso hace que sea más fácil. – Illandril

+0

Sugiero que dedique algo de tiempo a leer sobre ellos, aunque tienen un uso muy específico, ¡son bastante útiles para esos usos de nicho! –

0

Puede usar Function.prototype.bind() para esto. Es una adición de ES5.

Además del uso común de establecer el contexto de una función (valor this), también puede establecer argumentos parciales.

function out(a, b) { 
 
    document.write(a + " " + b); 
 
} 
 

 
function setter(func) { 
 
    return func.bind.apply(func, [window].concat([].slice.call(arguments).slice(1))); 
 
} 
 

 
setter(out, "hello")("world"); 
 
setter(out, "hello", "world")();

Mi función setter es realmente muy simple. La parte más larga es simplemente obtener la lista de argumentos. Voy a romper el código como el siguiente:

func.bind.apply(func, [window].concat([].slice.call(arguments).slice(1))) 
func.bind.apply(              ) // need to use apply to pass multiple arguments as an array to bind() 
       func,              // apply needs a context to be run in 
         [window].concat(        ) // pass an array of arguments to bind(), starting with window, to be the global context 
             [].slice.call(arguments).slice(1) // convert the arguments list to an array, and chop off the initial value 

es apoyado en these browsers: Cromo 7+, Firefox 4, IE9 +. MDN (vinculado al principio) tiene un polyfill sin embargo.

0
function myLog(arg1,arg2,arg3){ 
    return function(){ 
     //you can use the arguments from the parent scope 
     console.log(arg1+" "+arg2+" "+arg3); 
    }; 
} 
var passMeAnywhere = myLog(1,2,3); 
//execute the function, args will be in the immediate lexical scope. 
passMeAnywhere(); 
//One of the previous posts showed a way for context binding 
//Context binding allows any context 
//New is not required with binding 
+0

no funcionó. ¿Has probado esto tú mismo? –

1

El uso de Javascript apply(), se puede modificar la function prototype

Function.prototype.pass = function() { 
    var args = arguments, 
     func = this; 
    return function() { 
     func.apply(this, args); 
    } 
}; 

entonces le puede llamar como out.pass('hello','world')

apply toma una matriz para el segundo argumento/parámetro.

arguments es una función disponible dentro de la función que contiene todos los parámetros en la matriz como estructura.

Otra forma común de hacer esto es utilizar bind

loadedFunc = func.bind(this, v1, v2, v3);

continuación

loadedFunc() === this.func(v1,v2,v3);

esto suficiente para un poco, a pesar de que poco feo.

+1

Creo que debería agregar una explicación adecuada de cómo funciona esto; También se debe incluir un ejemplo de la función de OP. – sintakonte

+1

Mucha gente está en contra de las modificaciones de los prototipos básicos para propósitos triviales, pero este parece ideal, manteniendo la legibilidad en todo su código. –

+0

personalmente creo que acabo de utilizar bind para la mayoría de los casos, añadiré un ejemplo de cómo –

Cuestiones relacionadas