web-dev-qa-db-fra.com

Pourquoi Javascript ne lie-t-il pas correctement mon expression de point?

Je me demande si les méthodes d'abstraction des points (par exemple, dog.bark) se lient au moment de l'exécution ou à la compilation. Ma question concerne le code suivant, qui génère une erreur:

(true ? ''.toLowerCase : ''.toUpperCase)()

Mais ce qui suit ne le fait pas:

true ? ''.toLowerCase() : ''.toUpperCase()

Pourquoi ma chaîne littérale '' n'est-elle pas résolue dans le premier exemple?

28
Simon Kuang

C’est en fait assez simple une fois que vous avez compris comment les méthodes fonctionnent en javascript dans les coulisses.

toUpperCase est une méthode. C'est une fonction qui opère sur un objet spécifique ... généralement via la variable this.

Javascript est un langage prototype ... ce qui signifie que les fonctions attachées aux objets ne sont que des fonctions et peuvent être copiées. Lorsque vous appelez une méthode, certains travaux en arrière-plan garantissent que this est défini correctement, mais ce travail ne se produit que lorsque vous l'appelez en tant que méthode ... comme dans le formulaire obj.method()

En d'autres termes: ''.toUpperCase() s'assure que this est défini sur la chaîne '' lorsque vous l'appelez.

Lorsque vous l'appelez en tant que toUpperCase()this n'est pas défini sur quoi que ce soit en particulier (différents environnements font différentes choses avec this dans ce cas) 

Ce que fait votre code pourrait être réécrit comme ceci:

var function_to_call;
 if (true) {
    function_to_call = ''.toLowerCase;
 } else {
    function_to_call = ''.toUpperCase;
 }

 function_to_call();

Parce que votre appel de fonction: function_to_call() n'est pas dans la syntaxe object.method(), la chose qui définit this sur le bon objet n'est pas effectuée et votre appel de fonction s'exécute avec this non défini sur ce que vous souhaitez.

Comme d'autres personnes l'ont fait remarquer, vous pouvez utiliser func.call(thing_to_make_this) ou func.apply() pour y associer explicitement la chose correcte.

Je trouve beaucoup plus utile d'utiliser .bind() - qui est extrêmement sous-utilisé à mon avis. function_name.bind(this_object) vous donne une nouvelle fonction qui aura toujours this attaché à la chose correcte:

// assuming function_to_call is set
function_that_works = function_to_call.bind(my_object)

function_that_works(); // equivalent to my_object.function_to_call()

et cela signifie que vous pouvez faire passer la fonction que vous obtenez de bind() comme une fonction normale, et cela fonctionnera sur l'objet que vous voulez. Ceci est particulièrement utile dans les rappels, car vous pouvez créer une fonction anonyme liée à l'objet pour lequel elle a été créée:

// this won't work because when this runs, 'this' doesn't mean what you think
setTimeout(function() { this.display_message('success'); }, 2000);

// this will work, because we have given setTimeout a pre-bound function.
setTimeout(function() { this.display_message('success'); }.bind(this), 2000); 

TL; DR: Vous ne pouvez pas appeler une méthode en tant que fonction et vous attendre à ce qu'elle fonctionne, car elle ne sait pas ce que devrait être this. Si vous souhaitez utiliser cette fonction, vous devez utiliser .call(), .apply() ou .bind() pour vous assurer que this est défini correctement au moment de son exécution.

J'espère que cela pourra aider.

7
JayKuri
(true ? ''.toLowerCase : ''.toUpperCase)()

est équivalent à:

String.prototype.toLowerCase.call()
// or:
String.prototype.toLowerCase.call(undefined)

Cependant, 

true ? ''.toLowerCase() : ''.toUpperCase()

est équivalent à:

String.prototype.toLowerCase.call('')

Dans les deux cas, le premier argument de call est converti en un objet auquel la référence this dans String.prototype.toLowerCase fera référence. 

undefined ne peut pas être converti en objet, mais la chaîne vide peut:

function logThis () { console.log(this); }

logThis.call('');

La console d'extrait SO n'affiche que {}, mais c'est en fait la même chose que vous obtenez de new String(''). Lisez à propos du wrapper de chaîne sur MDN .

21
PeterMader

Parce que ces méthodes s'appliquent au contexte this et que, dans votre exemple, this est undefined

Une façon de remplacer this variable en utilisant la méthode bind:

(true ? ''.toLowerCase : ''.toUpperCase).bind('Hello')();

cela retournera hello

8
amd

Parce que lorsque vous faites (true ? ''.toLowerCase : ''.toUpperCase)(), vous n’appelez pas la fonction liée à une chaîne. Vous appelez simplement la fonction sans aucun contexte.

Prenons l'exemple suivant:

var obj = {
    objname: "objname",
    getName: function() {
        return this.objname;
    }
}

Lorsque vous l'appelez avec obj.getName(), la valeur est renvoyée correctement, mais lorsque vous procédez comme suit:

var fn = obj.getName
fn() // returns undefined because `fn` is not bound to `obj`
4
Saravana

Dans votre premier exemple, la fonction toLowerCase est détachée de son contexte (l'objet chaîne vide) puis invoquée. Puisque vous ne rattachez pas la fonction à quoi que ce soit, sa situation est undefined.

Ce comportement existe pour permettre la réutilisation de code via des mix-ins:

var obj1 = {
   name: "obj1",
   getName: function() { return this.name; }
}

var obj2 = {
   name: "obj2",
}

obj2.getName = obj1.getName //now obj2 has the getName method with correct context

console.log(obj2.getName())

0
Dmitry