Los objetos generalmente se crean para representar entidades del mundo real, como usuarios, pedidos, etc.:
dejar usuario = { nombre: "Juan", edad: 30 };
Y, en el mundo real, un usuario puede actuar : seleccionar algo del carrito de compras, iniciar sesión, cerrar sesión, etc.
Las acciones se representan en JavaScript mediante funciones en las propiedades.
Para empezar, enseñemos al user
a saludar:
dejar usuario = { nombre: "Juan", edad: 30 }; usuario.sayHola = función() { alerta("¡Hola!"); }; usuario.sayHola(); // ¡Hola!
Aquí acabamos de usar una expresión de función para crear una función y asignarla a la propiedad user.sayHi
del objeto.
Entonces podemos llamarlo user.sayHi()
. ¡El usuario ahora puede hablar!
Una función que es propiedad de un objeto se llama método .
Entonces, aquí tenemos un método sayHi
del objeto user
.
Por supuesto, podríamos usar una función predeclarada como método, como este:
dejar usuario = { //... }; // primero declarar función decir Hola() { alerta("¡Hola!"); } //luego lo agregamos como método usuario.decirHola = decirHola; usuario.sayHola(); // ¡Hola!
Programación orientada a objetos
Cuando escribimos nuestro código usando objetos para representar entidades, eso se llama programación orientada a objetos, en resumen: "OOP".
La programación orientada a objetos es una gran cosa, una ciencia interesante en sí misma. ¿Cómo elegir las entidades adecuadas? ¿Cómo organizar la interacción entre ellos? Eso es arquitectura, y hay excelentes libros sobre ese tema, como "Patrones de diseño: elementos de software reutilizable orientado a objetos" de E. Gamma, R. Helm, R. Johnson, J. Vissides o "Análisis y diseño orientado a objetos con Aplicaciones” de G. Booch y más.
Existe una sintaxis más corta para los métodos en un objeto literal:
// estos objetos hacen lo mismo usuario = { decir Hola: función() { alerta("Hola"); } }; // la abreviatura del método se ve mejor, ¿verdad? usuario = { decirHola() { // igual que "decirHola: función(){...}" alerta("Hola"); } };
Como se demostró, podemos omitir "function"
y simplemente escribir sayHi()
.
A decir verdad, las notaciones no son totalmente idénticas. Existen diferencias sutiles relacionadas con la herencia de objetos (que se abordarán más adelante), pero por ahora no importan. En casi todos los casos, se prefiere la sintaxis más corta.
Es común que un método de objeto necesite acceder a la información almacenada en el objeto para realizar su trabajo.
Por ejemplo, el código dentro de user.sayHi()
puede necesitar el nombre del user
.
Para acceder al objeto, un método puede utilizar la palabra clave this
.
El valor de this
es el objeto “antes del punto”, el que se usa para llamar al método.
Por ejemplo:
dejar usuario = { nombre: "Juan", edad: 30, decir Hola() { // "este" es el "objeto actual" alerta(este.nombre); } }; usuario.sayHola(); // John
Aquí, durante la ejecución de user.sayHi()
, el valor de this
será user
.
Técnicamente, también es posible acceder al objeto sin this
, haciendo referencia a él a través de la variable externa:
dejar usuario = { nombre: "Juan", edad: 30, decir Hola() { alerta(nombre.usuario); // "usuario" en lugar de "este" } };
…Pero ese código no es confiable. Si decidimos copiar user
a otra variable, por ejemplo admin = user
y sobrescribir user
con otra cosa, accederá al objeto incorrecto.
Eso se demuestra a continuación:
dejar usuario = { nombre: "Juan", edad: 30, decir Hola() { alerta (nombre.usuario); // conduce a un error } }; dejar administrador = usuario; usuario = nulo; // sobrescribir para hacer las cosas obvias admin.decirHola(); // TypeError: no se puede leer la propiedad 'nombre' de nulo
Si usáramos this.name
en lugar de user.name
dentro de la alert
, entonces el código funcionaría.
En JavaScript, la palabra clave this
se comporta a diferencia de la mayoría de los otros lenguajes de programación. Se puede utilizar en cualquier función, incluso si no es un método de un objeto.
No hay ningún error de sintaxis en el siguiente ejemplo:
función decir Hola() { alerta (este.nombre); }
El valor de this
se evalúa durante el tiempo de ejecución, según el contexto.
Por ejemplo, aquí la misma función está asignada a dos objetos diferentes y tiene "esto" diferente en las llamadas:
dejar usuario = { nombre: "Juan" }; let admin = { nombre: "Administrador" }; función decir Hola() { alerta (este.nombre); } // usa la misma función en dos objetos usuario.f = decir Hola; admin.f = decir Hola; // estas llamadas tienen this diferente // "esto" dentro de la función es el objeto "antes del punto" usuario.f(); // John (este == usuario) administrador.f(); // Administrador (este == administrador) administrador['f'](); // Administrador (el punto o los corchetes acceden al método, no importa)
La regla es simple: si se llama obj.f()
, entonces this
es obj
durante la llamada de f
. Entonces es user
o admin
en el ejemplo anterior.
Llamar sin objeto: this == undefined
Incluso podemos llamar a la función sin ningún objeto:
función decir Hola() { alerta(esto); } decir Hola(); // indefinido
En este caso, this
no está undefined
en modo estricto. Si intentamos acceder a this.name
, habrá un error.
En modo no estricto, el valor de this
en tal caso será el objeto global ( window
en un navegador, veremos esto más adelante en el capítulo Objeto global). Este es un comportamiento histórico que "use strict"
.
Generalmente dicha llamada es un error de programación. Si hay this
dentro de una función, espera ser llamado en el contexto de un objeto.
Las consecuencias de desatar this
Si vienes de otro lenguaje de programación, entonces probablemente estés acostumbrado a la idea de "vincular this
", donde los métodos definidos en un objeto siempre this
referencia a ese objeto.
En JavaScript this
es "gratuito", su valor se evalúa en el momento de la llamada y no depende de dónde se declaró el método, sino de qué objeto está "antes del punto".
El concepto de tiempo de ejecución evaluado this
tiene ventajas y desventajas. Por un lado, una función se puede reutilizar para diferentes objetos. Por otro lado, una mayor flexibilidad crea más posibilidades de cometer errores.
Aquí nuestra posición no es juzgar si esta decisión de diseño del lenguaje es buena o mala. Entenderemos cómo trabajar con él, cómo obtener beneficios y evitar problemas.
Las funciones de flecha son especiales: no tienen su "propio" this
. Si hacemos referencia this
desde dicha función, se toma de la función "normal" externa.
Por ejemplo, aquí arrow()
usa this
del método externo user.sayHi()
:
dejar usuario = { nombre: "Ilya", decir Hola() { let flecha = () => alerta(this.firstName); flecha(); } }; usuario.sayHola(); // Ilya
Esa es una característica especial de las funciones de flecha, es útil cuando en realidad no queremos tener un this
separado, sino tomarlo del contexto externo. Más adelante en el capítulo Funciones de flecha revisadas, profundizaremos en las funciones de flecha.
Las funciones que se almacenan en las propiedades del objeto se denominan "métodos".
Los métodos permiten que los objetos "actúen" como object.doSomething()
.
Los métodos pueden hacer referencia al objeto como this
.
El valor de this
se define en tiempo de ejecución.
Cuando se declara una función, puede usar this
, pero this
no tiene valor hasta que se llama a la función.
Una función se puede copiar entre objetos.
Cuando se llama a una función en la sintaxis de "método": object.method()
, el valor de this
durante la llamada es object
.
Tenga en cuenta que las funciones de flecha son especiales: no tienen this
. Cuando se accede this
dentro de una función de flecha, se toma desde afuera.
importancia: 5
Aquí la función makeUser
devuelve un objeto.
¿Cuál es el resultado de acceder a su ref
? ¿Por qué?
función crearUsuario() { devolver { nombre: "Juan", referencia: esto }; } dejar usuario = hacerUsuario(); alerta (usuario.ref.nombre); // ¿Cuál es el resultado?
Respuesta: un error.
Pruébalo:
función crearUsuario() { devolver { nombre: "Juan", referencia: esto }; } dejar usuario = hacerUsuario(); alerta (usuario.ref.nombre); // Error: no se puede leer la propiedad 'nombre' de indefinido
Esto se debe a que las reglas que establecen this
no tienen en cuenta la definición del objeto. Sólo importa el momento de la llamada.
Aquí el valor de this
dentro de makeUser()
no está undefined
, porque se llama como una función, no como un método con sintaxis de "punto".
El valor de this
es uno para toda la función, los bloques de código y los literales de objetos no lo afectan.
Entonces ref: this
en realidad toma la corriente this
de la función.
Podemos reescribir la función y devolver lo this
con un valor undefined
:
función hacerUsuario(){ devolver esto; // esta vez no hay ningún objeto literal } alerta( makeUser().nombre ); // Error: no se puede leer la propiedad 'nombre' de indefinido
Como puede ver, el resultado de alert( makeUser().name )
es el mismo que el resultado de alert( user.ref.name )
del ejemplo anterior.
Aquí está el caso opuesto:
función crearUsuario() { devolver { nombre: "Juan", referencia() { devolver esto; } }; } dejar usuario = hacerUsuario(); alerta (usuario.ref().nombre); // John
Ahora funciona, porque user.ref()
es un método. Y el valor de this
se establece en el objeto antes del punto .
.
importancia: 5
Cree una calculator
de objetos con tres métodos:
read()
solicita dos valores y los guarda como propiedades de objeto con nombres a
y b
respectivamente.
sum()
devuelve la suma de los valores guardados.
mul()
multiplica los valores guardados y devuelve el resultado.
deja calculadora = { //...tu código... }; calculadora.read(); alerta( calculadora.suma() ); alerta( calculadora.mul() );
Ejecute la demostración
Abra una caja de arena con pruebas.
deja calculadora = { suma() { devolver esto.a + esto.b; }, múl() { devolver esto.a * esto.b; }, leer() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; calculadora.read(); alerta( calculadora.suma() ); alerta( calculadora.mul() );
Abra la solución con pruebas en un sandbox.
importancia: 2
Hay un objeto ladder
que te permite subir y bajar:
dejar escalera = { paso: 0, arriba() { este.paso++; }, abajo() { este.paso--; }, showStep: function() { // muestra el paso actual alerta (este.paso); } };
Ahora bien, si necesitamos realizar varias llamadas en secuencia, podemos hacerlo así:
escalera.arriba(); escalera.arriba(); escalera.abajo(); escalera.mostrarPaso(); // 1 escalera.abajo(); escalera.mostrarPaso(); // 0
Modifique el código de up
, down
y showStep
para que las llamadas se puedan encadenar, así:
escalera.arriba().arriba().abajo().showStep().down().showStep(); // muestra 1 y luego 0
Este enfoque se utiliza ampliamente en las bibliotecas de JavaScript.
Abra una caja de arena con pruebas.
La solución es devolver el objeto mismo de cada llamada.
dejar escalera = { paso: 0, arriba() { este.paso++; devolver esto; }, abajo() { este.paso--; devolver esto; }, mostrarPaso() { alerta (este.paso); devolver esto; } }; escalera.arriba().arriba().abajo().showStep().down().showStep(); // muestra 1 y luego 0
También podemos escribir una única llamada por línea. Para cadenas largas es más legible:
escalera .arriba() .arriba() .abajo() .mostrarPaso() // 1 .abajo() .mostrarPaso(); // 0
Abra la solución con pruebas en un sandbox.