Come e perché funziona 'a' ['toUpperCase'] () in JavaScript?


209

JavaScript continua a sorprendermi e questa è un'altra istanza. Mi sono appena imbattuto in un codice che all'inizio non ho capito. Quindi ho eseguito il debug e sono arrivato a questo risultato:

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

Ora questo deve essere ovvio se toUpperCase()è definito come un membro del tipo stringa, ma inizialmente non aveva senso per me.

Comunque,

  • funziona perché toUpperCaseè un membro di "a"? O c'è qualcos'altro dietro le quinte?
  • il codice che stavo leggendo ha una funzione come segue:

    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

    È una specie di funzione generica chiamare QUALSIASI metodo su QUALSIASI oggetto. Ciò significa che il metodo specificato sarà già un membro implicito dell'oggetto specificato?

Sono sicuro che mi manca una seria comprensione del concetto di base delle funzioni JavaScript. Per favore aiutatemi a capire questo.


24
Esistono due modi per accedere alle proprietà di un oggetto: notazione punto e notazione parentesi. Leggermente correlato: stackoverflow.com/a/11922384/218196 . Conoscete già circa la notazione staffa perché si usa sempre quando si accede a elementi di un array: arr[5]. Se i numeri in cui i nomi identificatore valido è possibile utilizzare la notazione del punto: arr.5.
Felix Kling

2
È lo stesso di 5['toString']().
0x499602D2


Lettura correlata: 1) Ereditarietà e catena di prototipi: developer.mozilla.org/en-US/docs/JavaScript/Guide/… 2) La vita segreta dei primitivi JavaScript: javascriptweblog.wordpress.com/2010/09/09/27/…
Theraot,

21
A prima vista ho pensato che il titolo fosse "come e perché funziona JavaScript?" Ah bene.
Problematico

Risposte:


293

Per scomporlo.

  • .toUpperCase() è un metodo di String.prototype
  • 'a'è un valore primitivo, ma viene convertito nella sua rappresentazione Oggetto
  • Abbiamo due possibili notazioni per accedere alle proprietà / metodi dell'oggetto, la notazione punto e parentesi

Così

'a'['toUpperCase'];

è l'accesso tramite notazione di parentesi sulla proprietà toUpperCase, da String.prototype. Poiché questa proprietà fa riferimento a un metodo , possiamo richiamarlo allegando()

'a'['toUpperCase']();

Questa sarebbe una domanda di intervista divertente
FluffyBeing

78

foo.bare foo['bar']sono uguali, quindi il codice che hai pubblicato è lo stesso di

alert('a'.toUpperCase())

Quando si utilizza foo[bar](notare la mancanza di virgolette) non si utilizza il nome letterale barma qualunque sia il valore contenuto nella variabile bar. Quindi l'utilizzo della foo[]notazione invece di foo.consente di utilizzare un nome di proprietà dinamico.


Diamo un'occhiata a callMethod:

Prima di tutto, restituisce una funzione che assume objcome argomento. Quando quella funzione viene eseguita, chiamerà methodquell'oggetto. Quindi il metodo dato deve solo esistere su objse stesso o da qualche parte nella sua catena di prototipi.

Nel caso in cui toUpperCasequesto metodo provenga String.prototype.toUpperCase, sarebbe piuttosto stupido avere una copia separata del metodo per ogni singola stringa esistente.


25

Puoi accedere ai membri di qualsiasi oggetto con .propertyNamenotazione o ["propertyName"]notazione. Questa è la caratteristica del linguaggio JavaScript. Per essere sicuri che il membro sia nell'oggetto, controlla semplicemente se è definito:

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

Fondamentalmente javascript considera tutto come un oggetto, o meglio ogni oggetto può essere visto come un dizionario / array associativo. E funzioni / metodi sono definiti esattamente allo stesso modo per l'oggetto - come una voce in questo array associativo.

Quindi, essenzialmente, stai facendo riferimento / chiamando (nota la proprietà ' () ') 'toUpperCase', dell'oggetto 'a' (che è un tipo di stringa, in questo caso).

Ecco un po 'di codice nella parte superiore della mia testa:

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']è lo stesso di anyObject.anyPropertyNamequando anyPropertyNamenon ha personaggi problematici.

Vedi Lavorare con gli oggetti , da MDN.

Il toUpperCasemetodo è associato al tipo String. Quando chiami una funzione su un valore primitivo, qui 'a', viene automaticamente promossa a un oggetto, qui una stringa :

Nei contesti in cui un metodo deve essere invocato su una stringa di base o si verifica una ricerca di proprietà, JavaScript avvolgerà automaticamente la stringa di base e chiamerà il metodo o eseguirà la ricerca di proprietà.

Puoi vedere la funzione esiste registrandoti String.prototype.toUpperCase.


9

Quindi in Javascript lo objectssono objects. Cioè sono di questa natura {}. Le proprietà degli oggetti possono essere impostate usando uno di questi: a.greeting = 'hello';oa['greeting'] = 'hello'; . Entrambe le modalità funzionano.

Il recupero funziona allo stesso modo. a.greeting(senza virgolette) è 'hello', a['greeting']è'hello' . Eccezione: se la proprietà è un numero, funziona solo il metodo parentesi. Il metodo dot no.

Quindi 'a'è un oggetto con 'toUpperCase'proprietà che in realtà è una funzione. È possibile recuperare la funzione e richiamarla successivamente in entrambi i modi: 'a'.toUpperCase()o'a'['toUpperCase']() .

Ma il modo migliore per scrivere la funzione mappa sarebbe come

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

Chi ha bisogno della callMethodfunzione allora?


Splendidamente spiegato :-)
Elisha Senoo

6

Ogni oggetto JavaScript è una tabella hash quindi puoi accedere ai suoi membri specificando una chiave. ad esempio, se una variabile è una stringa, dovrebbe avere la funzione toUpperCase. Quindi, potresti invocarlo

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

quindi, per via inline, potresti avere sotto

"a"["toUpperCase"]()

6

toUpperCase è un metodo javascript standard: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase

Il motivo per cui funziona 'a'['toUpperCase']()è che la funzione toUpperCase è una proprietà dell'oggetto stringa 'a'. È possibile fare riferimento alle proprietà di un oggetto utilizzando object[property]o object.property. La sintassi 'a''toUpperCase' indica che stai facendo riferimento alla proprietà 'toUppercase' dell'oggetto stringa 'a', e quindi chiamandolo ().


4

Ciò significa che il metodo specificato sarà già un membro implicito dell'oggetto specificato?

No. Qualcuno potrebbe passare un oggetto che

  1. non ha una proprietà denominata toUpperCase ; o
  2. ha una proprietà denominata toUpperCaseche non è una funzione

Nel primo caso, verrà generato un errore perché l'accesso a una proprietà che non esiste ritorna undefinede non possiamo invocareundefined come funzione.

Nel secondo caso, verrà generato un errore perché, di nuovo, non possiamo invocare una non funzione come funzione.

Ricorda che JavaScript è un linguaggio molto poco tipizzato. La verifica del tipo è scarsa o assente a meno che e fino a quando non è necessario. Il codice che hai mostrato funziona in alcuni casi perché, in quei casi, l'oggetto passato ha una proprietà denominatatoUpperCase , che è una funzione.

Il fatto che l' objargomento non sia garantito per avere i giusti tipi di proprietà non disturba affatto JavaScript, per così dire. Adotta un atteggiamento "aspetta e vedi" e non genera un errore fino a quando non si verifica un problema reale in fase di esecuzione.


4

Quasi tutto in javascript può essere trattato come un oggetto. Nel tuo caso l'alfabeto stesso funge da oggetto stringa e toUpperCasepuò essere invocato come metodo. Le parentesi quadre sono solo un modo alternativo di accedere alle proprietà degli oggetti e dato che toUpperCaseè un metodo, quindi ()è necessaria la staffa semplice accanto alla ['toUpperCase']formatura ['toUpperCase']().

'a'['toUpperCase']() è equivalente a 'a'.toUpperCase()

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

3

La cosa importante da notare qui è che, dal momento che Javascript è un linguaggio dinamico , ogni oggetto è, essenzialmente, solo una mappa hash glorificata ( con alcune eccezioni ). E tutto in un oggetto Javascript è accessibile in due modi: notazione parentesi e notazione punto.

Esaminerò rapidamente le due notazioni che rispondono alla prima parte della domanda e poi passerò alla seconda parte.

Notazione parentesi

Questa modalità è più simile all'accesso a hashmap e array in altri linguaggi di programmazione. Puoi accedere a qualsiasi componente (dati (inclusi altri oggetti) o funzione) utilizzando questa sintassi.

Questo è esattamente ciò che stai facendo nel tuo esempio. Hai 'a', che è una stringa (e non un carattere letterale, come sarebbe in un linguaggio come C ++).

Usando la notazione parentesi, si accede al suo toUpperCasemetodo. Ma accedervi non è ancora sufficiente; semplicemente digitando alert, ad esempio, in Javascript, non si chiama il metodo. È solo una semplice affermazione. Per chiamare la funzione, è necessario aggiungere la parentesi: alert()mostra una semplice finestra di dialogo contenente undefined, in quanto non ha ricevuto parametri. Ora possiamo usare questa conoscenza per decifrare il tuo codice, che diventa:

alert('a'.toUpperCase());

Che è molto più leggibile.

In realtà, un buon modo per capirlo un po 'meglio è eseguire il seguente Javascript:

alert(alert)

Questo chiama anche alertpassandogli un oggetto funzione, anche alertsenza eseguire il secondo avviso. Ciò che viene mostrato (almeno in Chrome 26) è il seguente:

function alert() { [native code] } 

Calling:

alert(alert())

mostra due finestre di messaggio consecutive contenenti undefined. Questo è facile da spiegare: l'interno alert()viene eseguito per primo, mostra undefined(perché non aveva parametri) e non restituisce nulla. L'avviso esterno riceve il valore di ritorno dell'avviso interno, che è nulla e viene visualizzato anche undefinedin una finestra di messaggio.

Prova tutti i casi su jsFiddle!

Notazione a punti

Questo è l'approccio più standard, che consente l'accesso ai membri di un oggetto usando l' .operatore dot ( ). Ecco come apparirebbe il tuo codice nella notazione a punti:

alert('a'.toUpperCase())

Molto più leggibile. Quindi, quando dovremmo usare la notazione punto e quando dovremmo usare la notazione parentesi?

Confronto

La differenza principale tra i due metodi è semantica. Ci sono anche altri dettagli, ma li raggiungerò tra un secondo. La cosa più importante è ciò che realmente vuoi fare - una regola empirica è che usi la notazione a punti per campi e metodi ben definiti che un oggetto ha e la notazione a parentesi quadre per quando stai effettivamente usando il tuo oggetto come una mappa di hash .

Un ottimo esempio del motivo per cui questa regola è così importante può essere mostrato nel tuo esempio: poiché il codice utilizza la notazione tra parentesi in un punto in cui la notazione con punti sarebbe stata molto più sensata, rende il codice più difficile da leggere. E questa è una cosa negativa, perché il codice viene letto molte più volte di quanto non sia scritto .

In alcuni casi, è necessario utilizzare la notazione tra parentesi quadre anche se l'uso della notazione con punti era più sensato:

  • se un membro di un oggetto ha un nome contenente uno o più spazi o altri caratteri speciali, non puoi usare la notazione a punti: foo.some method()non funziona, ma foo["some method"]()funziona;

  • se devi accedere dinamicamente ai membri di un oggetto, sei anche bloccato usando la notazione parentesi;

Esempio:

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

La linea di fondo è che dovresti usare la sintassi della parentesi quando usi l'oggetto come hash map ( foods["burger"].eat()) e la sintassi del punto quando lavori con campi e metodi "reali" ( enemy.kill()). Dato che Javascript è un linguaggio dinamico, la linea tra i campi "reali" e i metodi di un oggetto e "altri" dati memorizzati può diventare piuttosto sfocata. Ma finché non li mescoli in modi confusi, dovresti andare bene.


Ora, sul resto della tua domanda (finalmente!: P).

come posso essere sicuro che il metodo sarà sempre un membro di obj

Non puoi. Provalo. Prova a chiamare derpuna stringa. Verrà visualizzato un errore nelle righe di:

Uncaught TypeError: Object a has no method 'derp' 

È una specie di funzione generica chiamare QUALSIASI metodo su QUALSIASI oggetto. Ciò significa che il metodo specificato sarà già un membro implicito dell'oggetto specificato?

Sì, nel tuo caso dovrebbe essere. Altrimenti si finisce con l'errore che ho menzionato sopra. Tuttavia, non è necessario utilizzare return obj[method]();nella callMethod()funzione. È possibile aggiungere la propria funzionalità che viene quindi utilizzata dalla funzione mappa. Ecco un metodo codificato che trasforma tutte le lettere in lettere maiuscole:

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

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

Il codice nell'esercitazione a cui si è collegati utilizza funzioni parziali . Sono un concetto complicato da soli. Leggere di più su questo argomento dovrebbe aiutare a rendere le cose più chiare di quanto potrei mai farle.


Nota: questo è il codice della funzione mappa utilizzata dal codice nella domanda, fonte qui .

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

2

Se stai chiedendo come funziona davvero, è così che l'ho letto. Ok, questa è una semplice funzione matematica. Per capirlo devi guardare la tabella ASCII. Che assegna un valore numerico a ciascuna lettera. Per convertirlo, il concorso utilizza semplicemente un'istruzione logica per convertirlo, ad esempio If (ChcrValue> 80 && charValue <106) // Questo è l'insieme di lettere minuscole quindi charValue = charValue - 38; // la distanza tra il set inferiore e il set superiore

è così semplice, in realtà non mi sono preoccupato di cercare i valori corretti, ma in pratica questo sta spostando tutte le lettere minuscole in maiuscolo.


In che modo ciò è legato alla domanda?
Quasdunk,

2
@glh, ha chiesto come e perché 'a'['toUpperCase']()funziona. Ma l'incomprensione è comprensibile, se qualcuno non ha letto la domanda.
LarsH

Risposta gr8, poiché non sono text-transform: uppercasestati aggiunti stili , ero alla fine su come JS è riuscito a cambiare il caso, a chiarire i dubbi e a imparare una cosa o due. molte grazie.
dkjain,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.