Comment et pourquoi fonctionne «a» [«toUpperCase»] () en JavaScript?


209

JavaScript continue de me surprendre et c'est un autre exemple. Je suis juste tombé sur un code que je ne comprenais pas au début. Je l'ai donc débogué et suis arrivé à cette conclusion:

alert('a'['toUpperCase']());  //alerts 'A'

Maintenant, cela doit être évident s'il toUpperCase()est défini comme un membre de type chaîne, mais cela n'avait pas de sens pour moi au départ.

En tous cas,

  • toUpperCaseEst- ce que cela fonctionne parce qu'il est membre de «a»? Ou il se passe autre chose dans les coulisses?
  • le code que je lisais a une fonction comme suit:

    function callMethod(method) {
        return function (obj) {
            return obj[method](); //**how can I be sure method will always be a member of obj**
        }
    }
    
    var caps2 = map(['a', 'b', 'c'], callMethod('toUpperCase')); // ['A','B','C'] 
    // ignoring details of map() function which essentially calls methods on every 
    // element of the array and forms another array of result and returns it

    C'est une fonction générique d'appeler TOUTES les méthodes sur TOUT objet. Mais cela signifie-t-il que la méthode spécifiée sera déjà un membre implicite de l'objet spécifié?

Je suis sûr que je manque une compréhension sérieuse du concept de base des fonctions JavaScript. S'il vous plaît, aidez-moi à comprendre cela.


24
Il existe deux façons d'accéder aux propriétés d'un objet: la notation par points et la notation par crochets. Légèrement lié: stackoverflow.com/a/11922384/218196 . Vous connaissez déjà sur la notation de support parce que vous utilisez toujours lors de l' accès des éléments du tableau: arr[5]. Si les numéros où les noms d'identification valides , vous pouvez utiliser la notation par points: arr.5.
Felix Kling

2
C'est la même chose que 5['toString']().
0x499602D2


Lecture connexe: 1) Héritage et chaîne de prototypes: developer.mozilla.org/en-US/docs/JavaScript/Guide/… 2) La vie secrète des primitives JavaScript: javascriptweblog.wordpress.com/2010/09/27/…
Theraot

21
Au début, j'ai pensé que le titre était "comment et pourquoi JavaScript fonctionne-t-il?" Et bien.
Problématique

Réponses:


293

Pour le décomposer.

  • .toUpperCase() est une méthode de String.prototype
  • 'a'est une valeur primitive, mais est convertie en sa représentation d'objet
  • Nous avons deux notations possibles pour accéder aux propriétés / méthodes des objets, la notation par points et crochets

Alors

'a'['toUpperCase'];

est l'accès via la notation entre crochets sur la propriété toUpperCase, à partir de String.prototype. Puisque cette propriété fait référence à une méthode , nous pouvons l'invoquer en attachant()

'a'['toUpperCase']();

Ce serait une question d'entrevue hilarante
FluffyBeing

78

foo.baret foo['bar']sont égaux donc le code que vous avez publié est le même que

alert('a'.toUpperCase())

Lorsque vous utilisez foo[bar](notez le manque de guillemets), vous n'utilisez pas le nom littéral barmais la valeur que barcontient la variable . Ainsi, l'utilisation de la foo[]notation au lieu de foo.vous permet d'utiliser un nom de propriété dynamique.


Jetons un œil à callMethod:

Tout d'abord, il renvoie une fonction qui prend objcomme argument. Lorsque cette fonction est exécutée, elle fera appel methodà cet objet. La méthode donnée doit donc simplement exister soit sur objelle-même, soit quelque part sur sa chaîne prototype.

Dans le cas où toUpperCasecette méthode vient de String.prototype.toUpperCase- il serait plutôt stupide d'avoir une copie séparée de la méthode pour chaque chaîne qui existe.


25

Vous pouvez accéder aux membres de n'importe quel objet avec une .propertyNamenotation ou une ["propertyName"]notation. C'est la caractéristique du langage JavaScript. Pour être sûr que le membre se trouve dans l'objet, vérifiez simplement s'il est défini:

function callMethod(method) {
    return function (obj) {
        if (typeof(obj[method]) == 'function') //in that case, check if it is a function
           return obj[method](); //and then invoke it
    }
}

17

Fondamentalement, javascript traite tout comme un objet, ou plutôt chaque objet peut être vu comme un dictionnaire / tableau associatif. Et les fonctions / méthodes sont définies exactement de la même manière pour l'objet - comme une entrée dans ce tableau associatif.

Donc, essentiellement, vous référencez / appelez (notez le ' () ') la propriété 'toUpperCase', de l'objet 'a' (qui est un type chaîne, dans ce cas).

Voici un code du haut de ma tête:

function myObject(){
    this.msg = "hey there! ;-)";
    this.woop = function(){
        alert(this.msg); //do whatever with member data
    }
}

var obj = new myObject();
alert( obj.msg );
alert( obj['msg'] );
obj['woop']();

10

anyObject['anyPropertyName']est le même que anyObject.anyPropertyNamelorsque anyPropertyNamen'a pas de caractères problématiques.

Voir Travailler avec des objets , à partir du MDN.

La toUpperCaseméthode est attachée au type String. Lorsque vous appelez une fonction sur une valeur primitive, ici 'a', elle est automatiquement promue en objet, ici une chaîne :

Dans les contextes où une méthode doit être invoquée sur une chaîne primitive ou une recherche de propriété se produit, JavaScript encapsule automatiquement la primitive de chaîne et appelle la méthode ou effectue la recherche de propriété.

Vous pouvez voir que la fonction existe en vous connectant String.prototype.toUpperCase.


9

Donc en Javascript, objectsc'est objects. C'est qu'ils sont de cette nature {}. Les propriétés des objets peuvent être définies à l'aide de l'une des options suivantes: a.greeting = 'hello';ou a['greeting'] = 'hello';. Les deux méthodes fonctionnent.

La récupération fonctionne de la même manière. a.greeting(sans guillemets) est 'hello', a['greeting']est 'hello'. Exception: si la propriété est un nombre, seule la méthode des crochets fonctionne. La méthode des points ne fonctionne pas.

Il en 'a'va de même pour un objet dont la 'toUpperCase'propriété est en fait une fonction. Vous pouvez récupérer la fonction et l'appeler ultérieurement dans les deux cas: 'a'.toUpperCase()ou 'a'['toUpperCase']().

Mais imo la meilleure façon d'écrire la fonction de carte serait alors:

var caps = ['a','b','c'].map( function(char) { return char.toUpperCase(); } )

Qui a alors besoin de la callMethodfonction?


Magnifiquement expliqué :-)
Elisha Senoo

6

Chaque objet JavaScript est une table de hachage, vous pouvez donc accéder à ses membres en spécifiant une clé. par exemple, si une variable est une chaîne, elle doit avoir la fonction toUpperCase. Vous pouvez donc l'invoquer en

var str = "a"
str['toUpperCase'](). // you get the method by its name as a key and invoke it.

donc, en ligne str, vous pourriez avoir ci-dessous

"a"["toUpperCase"]()

6

toUpperCase est une méthode javascript standard: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase

La raison pour laquelle cela fonctionne 'a'['toUpperCase']()est que la fonction toUpperCase est une propriété de l'objet chaîne 'a'. Vous pouvez référencer les propriétés d'un objet à l'aide de object[property]ou object.property. La syntaxe 'a''toUpperCase' indique que vous faites référence à la propriété 'toUppercase' de l'objet chaîne 'a', puis que vous l'appelez ().


4

Mais cela signifie-t-il que la méthode spécifiée sera déjà un membre implicite de l'objet spécifié?

Non. Quelqu'un pourrait passer dans un objet qui

  1. n'a pas de propriété nommée toUpperCase; ou
  2. a une propriété nommée toUpperCasequi n'est pas une fonction

Dans le premier cas, une erreur sera levée car l'accès à une propriété qui n'existe pas revient undefined, et nous ne pouvons pas invoquer en undefinedtant que fonction.

Dans le second cas, une erreur sera levée car encore une fois, nous ne pouvons pas invoquer une non-fonction en tant que fonction.

N'oubliez pas que JavaScript est un langage très lâche. Il n'y a que peu ou pas de vérification de type sauf si et jusqu'à ce qu'il le soit. Le code que vous avez montré fonctionne dans certains cas car, dans ces cas, l'objet transmis a une propriété nommée toUpperCase, c'est-à-dire une fonction.

Le fait que l' objargument ne soit pas garanti d'avoir les bons types de propriétés ne dérange pas du tout JavaScript, pour ainsi dire. Il adopte une attitude "attendre et voir" et ne génère pas d'erreur tant qu'un problème réel ne se produit pas au moment de l'exécution.


4

Presque tout en javascript peut être traité comme un objet. Dans votre cas, l'alphabet lui-même agit comme un objet chaîne et toUpperCasepeut être invoqué comme méthode. Les crochets ne sont qu'un moyen alternatif d'accéder aux propriétés des objets et puisqu'il toUpperCases'agit d'une méthode, le support simple ()est nécessaire à côté de la ['toUpperCase']formation ['toUpperCase']().

'a'['toUpperCase']() est équivalent à 'a'.toUpperCase()

'a'['toUpperCase']() // returns A
'a'.toUpperCase() // returns A

3

La chose importante à noter ici est que, puisque Javascript est un langage dynamique , chaque objet n'est, essentiellement, qu'une carte de hachage glorifiée ( à quelques exceptions près ). Et tout dans un objet Javascript est accessible de deux manières - la notation entre crochets et la notation par points.

Je passerai rapidement en revue les deux notations répondant à la première partie de votre question, puis je passerai à la deuxième partie.

Notation entre parenthèses

Ce mode est plus similaire à l'accès aux hashmaps et aux tableaux dans d'autres langages de programmation. Vous pouvez accéder à n'importe quel composant (données (y compris d'autres objets) ou fonction) à l'aide de cette syntaxe.

C'est exactement ce que vous faites dans votre exemple. Vous avez 'a', qui est une chaîne (et non un littéral de caractère, comme ce serait le cas dans un langage tel que C ++).

En utilisant la notation des crochets, vous accédez à sa toUpperCaseméthode. Mais y accéder n'est pas encore suffisant; le simple fait de taper alert, par exemple, en Javascript, n'appelle pas la méthode. C'est juste une simple déclaration. Pour appeler la fonction, vous devez ajouter la parenthèse: alert()affiche une boîte de dialogue simple contenant undefined, car elle n'a reçu aucun paramètre. Nous pouvons désormais utiliser ces connaissances pour déchiffrer votre code, qui devient:

alert('a'.toUpperCase());

Ce qui est beaucoup plus lisible.

En fait, un bon moyen de mieux comprendre cela est d'exécuter le Javascript suivant:

alert(alert)

Cela appelle alerten lui passant un objet fonction, également alert, sans exécuter également la deuxième alerte. Ce qui s'affiche (au moins dans Chrome 26) est le suivant:

function alert() { [native code] } 

Appel:

alert(alert())

affiche deux boîtes de message consécutives contenant undefined. Ceci est facile à expliquer: l'intérieur alert()est exécuté en premier, s'affiche undefined(car il n'a aucun paramètre) et ne renvoie rien. L'alerte externe reçoit la valeur de retour de l'alerte interne - qui n'est rien, et s'affiche également undefineddans une boîte de message.

Essayez tous les cas sur jsFiddle!

Notation par points

Il s'agit de l'approche plus standard, qui permet d'accéder aux membres d'un objet à l'aide de l' .opérateur dot ( ). Voici à quoi ressemblerait votre code en notation par points:

alert('a'.toUpperCase())

Beaucoup plus lisible. Alors, quand devrions-nous utiliser la notation par points, et quand devrions-nous utiliser la notation par crochets?

Comparaison

La principale différence entre les deux méthodes est sémantique. Il y a aussi d'autres détails, mais j'y reviendrai dans un instant. Ce qui est le plus important, c'est ce que vous voulez réellement faire - une règle de base est que vous utilisez la notation par points pour les champs et les méthodes bien établis d'un objet, et la notation par crochet pour quand vous utilisez réellement votre objet comme une carte de hachage .

Un bon exemple de la raison pour laquelle cette règle est si importante peut être montré dans votre exemple - puisque le code utilise la notation entre crochets à un endroit où la notation par points aurait été beaucoup plus sensible, cela rend le code plus difficile à lire. Et c'est une mauvaise chose, car le code est lu beaucoup plus de fois qu'il n'est écrit .

Dans certains cas, vous devez utiliser la notation entre crochets même si l'utilisation de la notation par points était plus judicieuse:

  • si un membre d'un objet a un nom contenant un ou plusieurs espaces ou tout autre caractère spécial, vous ne pouvez pas utiliser la notation par points: foo.some method()ne fonctionne pas, mais le foo["some method"]()fait;

  • si vous devez accéder dynamiquement aux membres d'un objet, vous êtes également bloqué en utilisant la notation entre crochets;

Exemple:

for(var i = 0; i < 10; ++i) {
   foo["method" + i](); 
 }

L'essentiel est que vous devez utiliser la syntaxe des crochets lorsque vous utilisez l'objet comme une carte de hachage ( foods["burger"].eat()) et la syntaxe des points lorsque vous travaillez avec des champs et des méthodes "réels" ( enemy.kill()). Avec Javascript étant un langage dynamique, la ligne entre les champs et les méthodes "réels" d'un objet et les "autres" données stockées peut devenir assez floue. Mais tant que vous ne les mélangez pas de manière confuse, tout devrait bien se passer.


Maintenant, passons au reste de votre question (enfin!: P).

comment puis-je être sûr que la méthode sera toujours membre de obj

Tu ne peux pas. Essayez-le. Essayez d'appeler derpsur une chaîne. Vous obtiendrez une erreur dans les lignes de:

Uncaught TypeError: Object a has no method 'derp' 

C'est une fonction générique d'appeler TOUTES les méthodes sur TOUT objet. Mais cela signifie-t-il que la méthode spécifiée sera déjà un membre implicite de l'objet spécifié?

Oui, dans votre cas, ce devrait être le cas. Sinon, vous vous retrouvez avec l'erreur que j'ai mentionnée ci-dessus. Cependant, vous n'avez pas besoin de l'utiliser return obj[method]();dans la callMethod()fonction. Vous pouvez ajouter votre propre fonctionnalité qui sera ensuite utilisée par la fonction de carte. Voici une méthode codée en dur qui transforme toutes les lettres en lettres majuscules:

function makeCap()
{
    return function(obj) {
        return obj.toUpperCase();
    }
}

var caps2 = map(['a', 'b', 'c'], makeCap()); // ['A','B','C'] 
console.log(caps2)

Le code du didacticiel auquel vous avez lié utilise des fonctions partielles . C'est un concept délicat en soi. En lire plus sur ce sujet devrait aider à rendre les choses plus claires que je ne pourrais jamais les faire.


Remarque: il s'agit du code de la fonction de carte utilisé par le code de la question, source ici .

function map(arr, iterator) {
  var narr = [];
  for (var i = 0; i < arr.length; i++) narr.push(iterator(arr[i], i));
  return narr;
}

2

Si vous demandez comment cela fonctionne réellement, c'est ainsi que je le lis. Ok, c'est une simple fonction mathématique. Pour le comprendre, vous devez regarder le tableau ascii. Qui attribue une valeur numérique à chaque lettre. Pour le dissimuler, le concurrent utilise simplement une instruction logique pour le convertir, par exemple, si (ChcrValue> 80 && charValue <106) // Il s'agit de l'ensemble des lettres minuscules, puis charValue = charValue - 38; // la distance entre l'ensemble inférieur et l'ensemble supérieur

c'est aussi simple que cela, je n'ai pas pris la peine de rechercher les valeurs correctes, mais cela déplace essentiellement toutes les lettres minuscules en majuscules.


Quel est le lien avec la question?
Quasdunk

2
@glh, il a demandé comment et pourquoi 'a'['toUpperCase']()travailler. Mais le malentendu est compréhensible, si quelqu'un n'a pas lu la question.
LarsH

gr8 réponse, car aucun style n'a text-transform: uppercaseété ajouté, j'étais à bout de mots sur la façon dont JS a réussi à changer le cas, a dissipé le doute et a appris une chose ou deux. Merci beaucoup.
dkjain
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.