web-dev-qa-db-fra.com

Comment puis-je pré-définir des arguments dans l'appel de fonction JavaScript? (Application de fonction partielle)

J'essaie d'écrire une fonction JavaScript qui renverra son premier argument (fonction) avec tout le reste de ses arguments en tant que paramètres prédéfinis pour cette fonction.

Alors:

fonction sur (a, b) {
 document.write (a + "" + b); 
} 

 fonction fonction (...) {...} 

 setter (sortie, "salut") (" monde "); 
 setter (dehors," bonjour "," monde ") (); 

Produirait "bonjour le monde" deux fois. pour certaines implémentation de setter

Je me suis heurté à un problème de manipulation du tableau d'arguments lors de mon premier essai, mais il semble qu'il y aurait un meilleur moyen de le faire.

53
AlexH

Tout d'abord, vous avez besoin d'un partiel - il y a une différence entre un partiel et un curry - et voici tout ce dont vous avez besoin, sans cadre:

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

Maintenant, en utilisant votre exemple, vous pouvez faire exactement ce que vous recherchez:

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 fonction partial() pourrait être utilisée pour implémenter, mais n'est pas currying. Voici une citation de un article de blog sur la différence :

Là où l'application partielle prend une fonction et crée une fonction qui prend moins d'arguments, Currying construit des fonctions qui prennent plusieurs arguments par composition de fonctions, chacune prenant un seul argument.

J'espère que cela pourra aider.

96
Jason Bunting

Est-ce que javascript au curry ce que vous cherchez?

3
Peter Bailey

Si vous utilisez Dojo, vous appelez simplement dojo.hitch () qui fait presque exactement ce que vous voulez. Presque - car il peut également être utilisé pour intégrer le contexte. Mais votre exemple est le premier:

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

Aussi bien que:

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 () est la partie de la base Dojo, donc dès que vous avez inclus dojo.js, il est là pour vous.

Un autre service général est disponible dans le module dojox.lang.functional.curry (documenté dans Amusement fonctionnel en JavaScript avec Dojo - il suffit de regarder "curry" sur cette page). Vous voudrez peut-être plus particulièrement examiner curry () et partial ().

curry () accumule les arguments (comme dans votre exemple) mais avec une différence: dès que l'arité est satisfaite, elle appelle la fonction qui renvoie la valeur. Mise en œuvre de votre exemple:

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

Notez que la dernière ligne n'a pas "()" à la fin - elle est appelée automatiquement.

partial () permet de remplacer des arguments au hasard:

df.partial(out, df.arg, "world")("hello");
2
Eugene Lazutkin

Vous pouvez utiliser Function.prototype.bind() pour cela. C'est un ajout ES5.

Outre le cas usuel de définition du contexte d'une fonction (valeur this), il est également possible de définir des arguments partiels.

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

Ma fonction setter est en réalité très simple. La partie la plus longue ne fait que recevoir la liste des arguments. Je vais casser le code comme ceci:

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

Il est pris en charge dans ces navigateurs : Chrome 7+, Firefox 4+, IE9 +. MDN (lié au début) a cependant un polyfill.

0
Scimonster

** EDIT: Voir la réponse de Jason Bunting. Cette réponse montre en fait un moyen sous-pair d'enchaîner de nombreux appels sortants, pas un seul appel sortant avec des préréglages pour certains des arguments. Si cette réponse aide réellement à résoudre un problème similaire, vous devez vous assurer d'utiliser apply et call comme le recommande Jason, au lieu de la manière obscure d'utiliser eval que j'ai imaginé ...

Eh bien ... votre out écrira beaucoup "indéfini" dans ceci ... mais cela devrait être proche de ce que vous voulez:

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

Je déplacerais probablement le code dans getArgString dans la fonction de définition elle-même, bien que ... un peu plus sûr depuis que j'ai utilisé 'eval's.

0
Illandril