[ Web Proxy ]
URL:
Viewing: https://fr.javascript.info/bind [Back]  [Original]

Le "bind" de fonction
FR

Nous souhaitons rendre ce projet open source disponible pour les gens du monde entier.

Aidez-nous traduire le contenu de ce tutoriel dans votre langue!

    Rechercher sur Javascript.info:
    Rechercher dans le tutoriel:
    Light themeDark theme
    DanskEnglishEspaolFranaisIndonesiaItalianoTrkeOzbek

    Lorsque lon transmet des mthodes objets en tant que callbacks, par exemple setTimeout, il y a un problme connu : la perte du this.

    Dans ce chapitre nous verrons les faons de rgler a.

    La perte du this

    Nous avons dj vu des exemples de la perte du this. Une fois quune mthode est passe quelque part sparement de lobjet this est perdu.

    Voici comment cela pourrait arriver avec setTimeout :

    let user = {
      firstName: "John",
      sayHi() {
        alert(`Hello, ${this.firstName}!`);
      }
    };
    
    setTimeout(user.sayHi, 1000); // Hello, undefined!

    Comme nous pouvons le voir, la sortie naffiche pas John pour this.firstName, mais undefined !

    Cest car setTimeout a eu la fonction user.sayHi, sparement de lobjet. La dernire ligne pourrait tre rcrite comme :

    let f = user.sayHi;
    setTimeout(f, 1000); // Perte du contexte d'user

    La mthode setTimeout dans le navigateur est un peu spciale : elle dfinit this=window pour lappel la fonction (pour Node.js, this devient un objet timer, mais a na pas dimportance ici). Donc pour this.firstName il essaye de rcuperer window.firstName, qui nexiste pas. Dans dautres cas similaires, this devient gnralement undefined.

    Cette tche est plutt commune on veut transmettre une mthode objet quelque part ailleurs (ici au scheduler) o elle sera appele. Comment sassurer quelle sera appele dans le bon contexte ?

    Solution 1 : Un wrapper

    La solution la plus simple est dutiliser une fonction enveloppe :

    let user = {
      firstName: "John",
      sayHi() {
        alert(`Hello, ${this.firstName}!`);
      }
    };
    
    setTimeout(function() {
      user.sayHi(); // Hello, John!
    }, 1000);

    Maintenant a fonctionne, car elle reoit user depuis un environnement lexical extrieur, et donc les appels la fonction se font normalement.

    La mme chose mais en plus court :

    setTimeout(() => user.sayHi(), 1000); // Hello, John!

    a lair bon, mais une lgre vulnrabilit apparat dans la structure de notre code.

    Que se passe t-il si avant le dclenchement de setTimeout (il y une seconde de dlai) user changeait de valeur ? Alors, soudainement, a appelera le mauvais objet !

    let user = {
      firstName: "John",
      sayHi() {
        alert(`Hello, ${this.firstName}!`);
      }
    };
    
    setTimeout(() => user.sayHi(), 1000);
    
    // ...La valeur d'user dans 1 seconde
    user = {
      sayHi() { alert("Another user in setTimeout!"); }
    };
    
    // Un autre user est dans le setTimeout !

    La prochaine solution garantit que ce genre de chose narrivera pas

    Solution 2 : bind

    Les fonctions fournissent une mthode intgre, bind qui permet de corriger this.

    La syntaxe basique est :

    // Une syntaxe plus complexe arrivera bientot
    let boundFunc = func.bind(context);

    Le rsultat de func.bind(context) est une objet exotique dans le style dune fonction, qui est appellable comme une fonction et qui transmet lappel func en dfinissant this=context de faon transparente.

    En dautres termes, appeller boundFunc quivaut func avec un this corrig.

    Par exemple, ici funcUser passe lappel this tel que this=user :

    let user = {
      firstName: "John"
    };
    
    function func() {
      alert(this.firstName);
    }
    
    let funcUser = func.bind(user);
    funcUser(); // John

    Ici func.bind(user) en tant variante lie de func, avec this=user.

    Tous les arguments sont passs loriginale func tels quels, par exemple :

    let user = {
      firstName: "John"
    };
    
    function func(phrase) {
      alert(phrase + ', ' + this.firstName);
    }
    
    // Lie this  user
    let funcUser = func.bind(user);
    
    funcUser("Hello"); // Hello, John (l'argument "Hello" est pass, et this=user)

    Maintenant essayons avec une mthode objet :

    let user = {
      firstName: "John",
      sayHi() {
        alert(`Hello, ${this.firstName}!`);
      }
    };
    
    let sayHi = user.sayHi.bind(user); // (*)
    
    // Peut s'excuter sans objet
    sayHi(); // Hello, John!
    
    setTimeout(sayHi, 1000); // Hello, John!
    
    // Mme si la valeur de user change dans 1 seconde
    // sayHi utilise la valeur pr-lie, laquelle fait rfrence  l'ancien objet user
    user = {
      sayHi() { alert("Another user in setTimeout!"); }
    };

    Sur la ligne (*) nous prenons la mthode user.sayHi en nous la lions user. La mthode sayHi est une fonction lie, qui peut tre appele seule ou tre transmise setTimeout a na pas dimportance, le contexte sera le bon.

    Ici, nous pouvons voir que les arguments passs tels quels, seulement this est corrig par bind :

    let user = {
      firstName: "John",
      say(phrase) {
        alert(`${phrase}, ${this.firstName}!`);
      }
    };
    
    let say = user.say.bind(user);
    
    say("Hello"); // Hello, John! (l'argument "Hello" est pass  say)
    say("Bye"); // Bye, John! (l'argument "Bye" est pass  say)
    La mthode pratique : bindAll

    Si un objet a plusieurs mthodes et que nous prvoyons de le transmettre plusieurs fois, alors on pourrait toutes les lier dans une boucle :

    for (let key in user) {
      if (typeof user[key] == 'function') {
        user[key] = user[key].bind(user);
      }
    }

    Les librairies JavaScript fournissent aussi des fonctions pratiques pour les liaisons de masse, e.g. _.bindAll(object, methodNames) avec lodash.

    Les fonctions partielles

    Jusqu maintenant nous avons parl uniquement de lier this. Allons plus loin.

    Nous pouvons lier this, mais aussi des arguments. Cest rarement utilis, mais a peut tre pratique.

    La syntaxe complte de bind :

    let bound = func.bind(context, [arg1], [arg2], ...);

    Elle permet de lier le contexte en tant que this et de dmarrer les arguments de la fonction.

    Par exemple, nous avons une fonction de multiplication mul(a, b) :

    function mul(a, b) {
      return a * b;
    }

    Utilisons bind pour crer une fonction double sur cette base :

    function mul(a, b) {
      return a * b;
    }
    
    let double = mul.bind(null, 2);
    
    alert( double(3) ); // = mul(2, 3) = 6
    alert( double(4) ); // = mul(2, 4) = 8
    alert( double(5) ); // = mul(2, 5) = 10

    Lappel mul.bind(null, 2) crer une nouvelle fonction double qui transmet les appels mul, corrigeant null dans le contexte et 2 comme premier argument. Les arguments sont passs tels quels plus loin.

    a sappelle lapplication de fonction partielle nous creons une nouvelle fonction en corrigeant certains paramtres dune fonction existante.

    Veuillez noter que nous nutilisons actuellement pas this ici. Mais bind en a besoin, donc nous devrions mettre quelque chose dedans comme null.

    La fonction triple dans le code ci-dessous triple la valeur :

    function mul(a, b) {
      return a * b;
    }
    
    let triple = mul.bind(null, 3);
    
    alert( triple(3) ); // = mul(3, 3) = 9
    alert( triple(4) ); // = mul(3, 4) = 12
    alert( triple(5) ); // = mul(3, 5) = 15

    Pourquoi faisons nous gnralement une fonction partielle ?

    Lavantage de faire a est que nous pouvons crer une fonction indpendante avec un nom lisible (double, triple). Nous pouvons les utiliser et ne pas fournir de premier argument chaque fois puisque cest corrig par bind.

    Dans dautres cas, les fonctions partielles sont utiles quand nous avons des fonctions vraiment gnriques et que nous voulons une variante moins universelle pour des raisons pratiques.

    Par exemple, nous avons une fonction send(from, to, text). Alors, dans un objet user nous pourrions vouloir en utiliser une variante partielle : sendTo(to, text) qui envoie depuis lutilisateur actuel.

    Aller dans les partielles sans contexte

    Que se passerait t-il si nous voulions corriger certains arguments, mais pas le contexte this ? Par exemple, pour une mthode objet.

    La fonction bind native ne permet pas a. Nous ne pouvons pas juste omettre le contexte et aller directement aux arguments.

    Heureusement, une fonction partial pour lier seulement les arguments peut tre facilement implmente.

    Comme a :

    function partial(func, ...argsBound) {
      return function(...args) { // (*)
        return func.call(this, ...argsBound, ...args);
      }
    }
    
    // Utilisation :
    let user = {
      firstName: "John",
      say(time, phrase) {
        alert(`[${time}] ${this.firstName}: ${phrase}!`);
      }
    };
    
    // Ajoute une mthode partielle avec time corrig
    user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());
    
    user.sayNow("Hello");
    // Quelque chose du genre :
    // [10:00] John: Hello!

    Le rsultat de lappel partial(func[, arg1, arg2...]) est une enveloppe (*) qui appelle func avec :

    • Le mme this quil rcupre (pour user.sayNow lappel est user)
    • Alors il donne ...argsBound les arguments provenant de lappel de partial ("10:00")
    • Alors il donne ...args les arguments donns lenveloppe ("Hello")

    Alors, cest simple faire avec la spread syntaxe, pas vrai ?

    Aussi il y a une implmentation de _.partial prte lemploi dans les librairies lodash.

    Rsum

    La mthode func.bind(context, ...args) retourne une variante lie de la fonction func qui corrige le contexte de this et des premiers arguments sils sont donns.

    Nous appliquons gnralement bind pour corriger this pour une mthode objet, comme a nous pouvons la passer ailleurs. Par exemple, setTimeout.

    Quand nous corrigeons certains arguments dune fonction existante, la fonction (moins universelle) en rsultant est dite partiellement applique ou partielle.

    Les fonctions partielles sont pratiques quand nous ne voulons pas rpter le mme argument encore et encore. Comme si nous avions une fonction send(from, to), et que from devait tre toujours le mme pour notre tche, nous pourrions rcuprer une partielle et continuer.

    Exercices

    importance: 5

    What will be the output?

    function f() {
      alert( this ); // ?
    }
    
    let user = {
      g: f.bind(null)
    };
    
    user.g();
    solution

    The answer: null.

    function f() {
      alert( this ); // null
    }
    
    let user = {
      g: f.bind(null)
    };
    
    user.g();

    The context of a bound function is hard-fixed. Theres just no way to further change it.

    So even while we run user.g(), the original function is called with this=null.

    importance: 5

    Can we change this by additional binding?

    What will be the output?

    function f() {
      alert(this.name);
    }
    
    f = f.bind( {name: "John"} ).bind( {name: "Ann" } );
    
    f();
    solution

    The answer: John.

    function f() {
      alert(this.name);
    }
    
    f = f.bind( {name: "John"} ).bind( {name: "Pete"} );
    
    f(); // John

    The exotic bound function object returned by f.bind(...) remembers the context (and arguments if provided) only at creation time.

    A function cannot be re-bound.

    importance: 5

    Theres a value in the property of a function. Will it change after bind? Why, or why not?

    function sayHi() {
      alert( this.name );
    }
    sayHi.test = 5;
    
    let bound = sayHi.bind({
      name: "John"
    });
    
    alert( bound.test ); // what will be the output? why?
    solution

    The answer: undefined.

    The result of bind is another object. It does not have the test property.

    importance: 5

    The call to askPassword() in the code below should check the password and then call user.loginOk/loginFail depending on the answer.

    But it leads to an error. Why?

    Fix the highlighted line for everything to start working right (other lines are not to be changed).

    function askPassword(ok, fail) {
      let password = prompt("Password?", '');
      if (password == "rockstar") ok();
      else fail();
    }
    
    let user = {
      name: 'John',
    
      loginOk() {
        alert(`${this.name} logged in`);
      },
    
      loginFail() {
        alert(`${this.name} failed to log in`);
      },
    
    };
    
    askPassword(user.loginOk, user.loginFail);
    solution

    The error occurs because ask gets functions loginOk/loginFail without the object.

    When it calls them, they naturally assume this=undefined.

    Lets bind the context:

    function askPassword(ok, fail) {
      let password = prompt("Password?", '');
      if (password == "rockstar") ok();
      else fail();
    }
    
    let user = {
      name: 'John',
    
      loginOk() {
        alert(`${this.name} logged in`);
      },
    
      loginFail() {
        alert(`${this.name} failed to log in`);
      },
    
    };
    
    askPassword(user.loginOk.bind(user), user.loginFail.bind(user));

    Now it works.

    An alternative solution could be:

    //...
    askPassword(() => user.loginOk(), () => user.loginFail());

    Usually that also works and looks good.

    Its a bit less reliable though in more complex situations where user variable might change after askPassword is called, but before the visitor answers and calls () => user.loginOk().

    importance: 5

    La tche est une variante un peu plus complexe de Fix a function that loses "this".

    Lobjet user a t modifi. Maintenant, au lieu de deux fonctions loginOk/loginFail, il a une seule fonction user.login(true/false).

    Que faire passer askPassword dans le code ci-dessous, de sorte quil appelle user.login(true) comme ok et user.login(false) comme fail ?

    function askPassword(ok, fail) {
      let password = prompt("Password?", '');
      if (password == "rockstar") ok();
      else fail();
    }
    
    let user = {
      name: 'John',
    
      login(result) {
        alert( this.name + (result ? ' logged in' : ' failed to log in') );
      }
    };
    
    askPassword(?, ?); // ?

    Vos modifications doivent uniquement modifier le fragment surlign.

    solution
    1. Soit utiliser une fonction wrapper, une flche pour tre concis:

      askPassword(() => user.login(true), () => user.login(false));

      Maintenant, elle obtient user des variables externes et lexcute normalement.

    2. Ou crez une fonction partielle partir de user.login qui utilise user comme contexte et a le bon premier argument:

      askPassword(user.login.bind(user, true), user.login.bind(user, false));
    Carte du tutoriel

    Commentaires

    lire ceci avant de commenter
    • Si vous avez des amliorations suggrer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
    • Si vous ne comprenez pas quelque chose dans l'article, merci de prciser.
    • Pour insrer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen)

    Web Proxy Viewer  |  New URL  |  Original Page