¿Cómo y por qué funciona 'a' ['toUpperCase'] () en JavaScript?


209

JavaScript me sigue sorprendiendo y esta es otra instancia. Acabo de encontrar un código que no entendí al principio. Así que lo depuré y llegué a este hallazgo:

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

Ahora, esto debe ser obvio si toUpperCase()se define como un miembro del tipo de cadena, pero no tenía sentido para mí inicialmente.

De todas formas,

  • ¿funciona esto porque toUpperCasees miembro de 'a'? ¿O hay algo más sucediendo detrás de escena?
  • El código que estaba leyendo tiene la siguiente función:

    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

    Es una función genérica llamar a CUALQUIER método en CUALQUIER objeto. ¿Pero eso significa que el método especificado ya será un miembro implícito del objeto especificado?

Estoy seguro de que me falta una comprensión seria del concepto básico de las funciones de JavaScript. Por favor, ayúdame a entender esto.


24
Hay dos formas de acceder a las propiedades de un objeto: notación de puntos y notación de corchetes. Ligeramente relacionado: stackoverflow.com/a/11922384/218196 . Usted ya conoce acerca notación de corchetes porque siempre se lo utiliza cuando se accede a elementos de la matriz: arr[5]. Si los números en los nombres de identificadores válidos que podrían utilizar la notación de puntos: arr.5.
Felix Kling

2
Es lo mismo que 5['toString']().
0x499602D2


Lectura relacionada: 1) Herencia y cadena de prototipos: developer.mozilla.org/en-US/docs/JavaScript/Guide/… 2) La vida secreta de los primitivos de JavaScript: javascriptweblog.wordpress.com/2010/09/27/…
Theraot

21
En la primera lectura, pensé que el título era "¿cómo y por qué funciona JavaScript?" Ah bueno.
Problemática

Respuestas:


293

Para descomponerlo.

  • .toUpperCase() es un método de String.prototype
  • 'a'es un valor primitivo, pero se convierte en su representación de Objeto
  • Tenemos dos posibles anotaciones para acceder a las propiedades / métodos de los objetos, la notación de puntos y corchetes.

Entonces

'a'['toUpperCase'];

es el acceso mediante notación de paréntesis en la propiedad toUpperCase, desde String.prototype. Como esta propiedad hace referencia a un método , podemos invocarlo adjuntando()

'a'['toUpperCase']();

Esta sería una pregunta hilarante para la entrevista
FluffyBeing

78

foo.bary foo['bar']son iguales, por lo que el código que publicaste es el mismo que

alert('a'.toUpperCase())

Cuando usas foo[bar] (tenga en cuenta la falta de comillas) no se usa el nombre literal barsino el valor que barcontenga la variable . Entonces, usar la foo[]notación en lugar de le foo.permite usar un nombre de propiedad dinámico.


Echemos un vistazo a callMethod:

En primer lugar, devuelve una función que toma objcomo argumento. Cuando se ejecuta esa función, llamará methoda ese objeto. Entonces, el método dado solo necesita existir en objsí mismo o en algún lugar de su cadena de prototipos.

En el caso de toUpperCaseque ese método provenga String.prototype.toUpperCase, sería bastante estúpido tener una copia separada del método para cada cadena que exista.


25

Puede acceder a los miembros de cualquier objeto con .propertyNamenotación o ["propertyName"]notación. Esa es la característica del lenguaje JavaScript. Para asegurarse de que ese miembro esté en el objeto, simplemente verifique si está definido:

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

Básicamente, JavaScript trata todo como un Objeto, o más bien, cada objeto puede verse como un diccionario / matriz asociativa. Y las funciones / métodos se definen exactamente de la misma manera para el objeto, como una entrada en esta matriz asociativa.

Entonces, esencialmente, estás haciendo referencia / llamando (nota el ' () propiedad ') 'toUpperCase', del objeto 'a' (que es un tipo de cadena, en este caso).

Aquí hay un código de la parte superior de mi cabeza:

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']es lo mismo que anyObject.anyPropertyNamecuando anyPropertyNameno tiene caracteres problemáticos.

Consulte Trabajar con objetos , de MDN.

El toUpperCasemétodo se adjunta al tipo String. Cuando llama a una función en un valor primitivo, aquí 'a', se promueve automáticamente a un objeto, aquí una Cadena :

En contextos donde se invoca un método en una cadena primitiva o se produce una búsqueda de propiedad, JavaScript ajustará automáticamente la cadena primitiva y llamará al método o realizará la búsqueda de propiedad.

Puede ver que la función existe al iniciar sesión String.prototype.toUpperCase.


9

Entonces, en Javascript, objectsson objects. Es decir, son de esta naturaleza {}. Las propiedades de los objetos se pueden establecer usando uno de estos: a.greeting = 'hello';o a['greeting'] = 'hello';. Ambas formas funcionan.

La recuperación funciona igual. a.greeting(sin comillas) es 'hello', a['greeting']es 'hello'. Excepción: si la propiedad es un número, solo funciona el método de paréntesis. El método de puntos no.

Entonces, 'a'es un objeto con 'toUpperCase'propiedad que en realidad es una función. Puede recuperar la función y llamarla posteriormente de cualquier manera: 'a'.toUpperCase()o'a'['toUpperCase']() .

Pero en mi opinión, la mejor manera de escribir la función del mapa sería como

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

¿Quién necesita la callMethodfunción entonces?


Muy bien explicado :-)
Elisha Senoo

6

Cada objeto de JavaScript es una tabla hash, por lo que puede acceder a sus miembros especificando una clave. por ejemplo, si una variable es una cadena, entonces debería tener la función toUpperCase. Entonces, podrías invocarlo

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

entonces, por str en línea, podrías tener abajo

"a"["toUpperCase"]()

6

toUpperCase es un método estándar de JavaScript: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/toUpperCase

La razón por la que funciona 'a'['toUpperCase']()es que la función toUpperCase es una propiedad del objeto de cadena 'a'. Puede hacer referencia a las propiedades de un objeto utilizando object[property]o object.property. La sintaxis 'a''toUpperCase' indica que hace referencia a la propiedad 'toUppercase' del objeto de cadena 'a' y luego lo llama ().


4

¿Pero eso significa que el método especificado ya será un miembro implícito del objeto especificado?

No. Alguien podría pasar un objeto que

  1. no tiene una propiedad nombrada toUpperCase; o
  2. tiene una propiedad llamada toUpperCaseque no es una función

En el primer caso, se generará un error porque acceder a una propiedad que no existe devuelve undefined, y no podemos invocar undefinedcomo función.

En el segundo caso, se generará un error porque, de nuevo, no podemos invocar una no función como función.

Recuerde que JavaScript es un lenguaje muy poco tipado. Se produce poca o ninguna verificación de tipo a menos que sea necesario. El código que mostró funciona en ciertos casos porque, en esos casos, el objeto pasado tiene una propiedad llamada toUpperCase, que es una función.

El hecho de que objno se garantice que el argumento tenga los tipos correctos de propiedades no molesta en absoluto a JavaScript, por así decirlo. Adopta una actitud de "esperar y ver", y no arroja un error hasta que se produce un problema real en el tiempo de ejecución.


4

Casi todo en javascript se puede tratar como un objeto. En su caso, el alfabeto mismo actúa como un objeto de cadena y toUpperCasepuede invocarse como método. Los corchetes son solo una forma alternativa de acceder a las propiedades de los objetos y, dado que toUpperCasees un método, el corchete simple ()se necesita al lado de la ['toUpperCase']formación ['toUpperCase']().

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

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

3

Lo importante a tener en cuenta aquí es que, dado que Javascript es un lenguaje dinámico , cada objeto es, esencialmente, solo un hash-map glorificado ( con algunas excepciones ). Y se puede acceder a todo en un objeto Javascript de dos maneras: notación de corchetes y notación de puntos.

Revisaré rápidamente las dos anotaciones respondiendo la primera parte de su pregunta, y luego pasaré a la segunda parte.

Notación de corchetes

Este modo es más similar al acceso a hashmaps y matrices en otros lenguajes de programación. Puede acceder a cualquier componente (datos (incluidos otros objetos) o función) utilizando esta sintaxis.

Esto es exactamente lo que estás haciendo en tu ejemplo. Tienes 'a', que es una cadena (y no un carácter literal, como si estuviera en un lenguaje como C ++).

Usando la notación de corchetes, accede a su toUpperCasemétodo Pero acceder a él todavía no es suficiente; simplemente escribir alert, por ejemplo, en Javascript, no llama al método. Es solo una declaración simple. Para llamar a la función, debe agregar el paréntesis: alert()muestra un cuadro de diálogo simple que contiene undefined, ya que no recibió parámetros. Ahora podemos usar este conocimiento para descifrar su código, que se convierte en:

alert('a'.toUpperCase());

Lo cual es mucho más legible.

En realidad, una buena manera de entender esto un poco mejor es ejecutar el siguiente Javascript:

alert(alert)

Esto llama alertal pasarle un objeto de función, también alert, sin ejecutar también la segunda alerta. Lo que se muestra (en Chrome 26, al menos) es lo siguiente:

function alert() { [native code] } 

Vocación:

alert(alert())

muestra dos cuadros de mensaje consecutivos que contienen undefined. Esto es fácil de explicar: el interno alert()se ejecuta primero, se muestra undefined(porque no tenía ningún parámetro) y no devuelve nada. La alerta externa recibe el valor de retorno de la alerta interna, que no es nada, y también se muestra undefineden un cuadro de mensaje.

¡Pruebe todos los casos en jsFiddle!

Notación de punto

Este es el enfoque más estándar, que permite acceder a los miembros de un objeto utilizando el .operador dot ( ). Así es como se vería su código en notación de puntos:

alert('a'.toUpperCase())

Mucho más legible. Entonces, ¿cuándo debemos usar la notación de puntos y cuándo debemos usar la notación de corchetes?

Comparación

La principal diferencia entre los dos métodos es semántica. También hay algunos otros detalles, pero los abordaré en un segundo. Lo más importante es lo que realmente quiere hacer: una regla general es que use la notación de puntos para los campos y métodos bien establecidos que tiene un objeto, y la notación de corchetes para cuando realmente está usando su objeto como un mapa hash .

Un gran ejemplo de por qué esta regla es tan importante se puede mostrar en su ejemplo, ya que el código está usando la notación de corchetes en un lugar donde la notación de puntos hubiera sido mucho más sensible, hace que el código sea más difícil de leer. Y eso es algo malo, porque el código se lee muchas veces más de lo que se escribe .

En algunos casos, debe usar la notación de corchetes, incluso si usar la notación de puntos fuera más sensato:

  • si un miembro de un objeto tiene un nombre que contiene uno o más espacios o cualquier otro carácter especial, no puede usar la notación de puntos: foo.some method()no funciona, pero foo["some method"]()sí;

  • si necesita acceder dinámicamente a los miembros de un objeto, también está atrapado usando la notación de corchetes;

Ejemplo:

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

La conclusión es que debe usar la sintaxis de paréntesis cuando use el objeto como un mapa hash ( foods["burger"].eat()) y la sintaxis de puntos cuando trabaje con campos y métodos "reales" ( enemy.kill()). Dado que Javascript es un lenguaje dinámico, la línea entre los campos "reales" y los métodos de un objeto y "otros" datos almacenados dentro puede volverse bastante borrosa. Pero siempre que no los mezcles de manera confusa, deberías estar bien.


Ahora, sobre el resto de su pregunta (¡finalmente!: P).

¿Cómo puedo estar seguro de que el método siempre será miembro de obj?

No puedes Intentalo. Intenta llamar derpa una cadena. Obtendrá un error en las líneas de:

Uncaught TypeError: Object a has no method 'derp' 

Es una función genérica llamar a CUALQUIER método en CUALQUIER objeto. ¿Pero eso significa que el método especificado ya será un miembro implícito del objeto especificado?

Sí, en tu caso tendría que ser así. De lo contrario, terminará con el error que mencioné anteriormente. Sin embargo, no tiene que usar return obj[method]();en la callMethod()función. Puede agregar su propia funcionalidad que luego utiliza la función de mapa. Aquí hay un método codificado que convierte todas las letras en mayúsculas:

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

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

El código en el tutorial al que se vinculó utiliza funciones parciales . Son un concepto complicado por sí mismos. Leer más sobre ese tema debería ayudar a aclarar las cosas de lo que podría hacerlo.


Nota: este es el código de la función de mapa utilizada por el código en la pregunta, fuente aquí .

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

2

Si me preguntas cómo funciona realmente, así es como lo leí. Ok, esta es una función matemática simple. Para entenderlo, debes mirar la tabla ascii. Que asigna un valor numérico a cada letra. Para encubrirlo, la competencia simplemente usa una instrucción lógica para encubrir, por ejemplo If (ChcrValue> 80 && charValue <106) // Este es el conjunto de letras minúsculas y luego charValue = charValue - 38; // la distancia entre el conjunto inferior y el conjunto superior

es así de simple, en realidad no me molesté en buscar los valores correctos, sin embargo, esto básicamente está cambiando todas las letras minúsculas a mayúsculas.


¿Cómo se relaciona esto con la pregunta?
Cuasdunk

2
@glh, preguntó cómo y por qué 'a'['toUpperCase']()trabajar. Pero el malentendido es comprensible si alguien no leyó la pregunta.
LarsH

gr8 respuesta, ya que no text-transform: uppercasese agregaron estilos , estaba al final de mi ingenio sobre cómo JS logró cambiar el caso, despejó dudas y aprendí una o dos cosas. muchas gracias.
dkjain
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.