Contexto
Desde los primeros días de la Web ha habido una necesidad de interfaces más dinámicas y receptivas.
Aunque está bien leer páginas HTML estáticas de texto y aún mejor cuando se presentan con la ayuda de CSS, es mucho más divertido interactuar con las aplicaciones en nuestros navegadores, como el correo electrónico, los calendarios, la banca, las compras, los dibujos, jugando juegos y edición de texto. Todo eso es posible gracias a JavaScript, el lenguaje de programación de la Web. JavaScript comenzó con líneas simples integradas en HTML, pero ahora se usa de formas mucho más sofisticadas. Los desarrolladores aprovechan la naturaleza orientada a objetos del lenguaje para construir arquitecturas de código escalables compuestas de piezas reutilizables.
En este tema exploraremos las capacidades de JavaScript como lengua orientado a objetos.
Explicación
11.1 Programación orientada a objetos
Tus aplicaciones de JavaScript pueden consistir completamente en funciones y variables, tanto locales como globales, pero si quieres garantizar la facilidad de reutilización, la compacidad del código y la eficiencia, así como el código que funciona bien con otras bibliotecas, vas a necesitar considerar oportunidades para encapsular tu código en objetos.
Afortunadamente, trabajar con objetos en JavaScript no es mucho más complicado que trabajar con funciones. Después de todo, una función de JavaScript es un objeto y todos los objetos son, técnicamente, funciones.
¿Confundido aún?
A diferencia de lenguajes como Java o C ++, que se basan en clases e instancias, JavaScript se basa en la herencia prototípica. Lo que significa herencia prototípica es que la reutilización ocurre al crear nuevas instancias de objetos existentes, en lugar de instancias de una clase. En lugar de que la extensibilidad se produzca a través de la herencia de clase, la extensibilidad del prototipo se logra mejorando un objeto existente con nuevas propiedades y métodos.
Objetos JavaScript
Supón que deseas crear un objeto de JavaScript personalizado y reutilizable.
Define un nuevo objeto explícitamente usando la sintaxis funcional y luego crea nuevas instancias, pasando los datos que el constructor de objetos está esperando:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> <script> function Tune(cancion, artista) { this.title = cancion; this.artista = artista; this.concat = function () { return this.title + "-" + this.artista; } } window.onload = function () { var cancionalegre = new Array(); cancionalegre[0] = new Tune("La culebra", "Jose Domingo"); // Imprime la cancion y el artista alert(cancionalegre[0].concat()); } </script> </body> </html>
Haz clic aquí para observar el resultado.
Los miembros en el constructor del objeto (el cuerpo de la función) no son accesibles fuera del objeto a menos que estén asignados a ese objeto usando la palabra “this”. Si están unidos al objeto utilizando la palabra “var”, sólo la función interna “Tune”y el método “concat” pueden acceder a estos miembros de datos ahora privados.
Expansión de objetos con prototipo
Si deseas dar más funcionalidad a un objeto existente con un nuevo método , usa el prototipo de objeto para extender el objeto:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
</head>
<body>
<script>
function Tune(cancion, artista) {
this.title = cancion;
this.artista = artista;
this.concat = function () {
return this.title + "-" + this.artista;
}
}
Tune.prototype.addCategory = function (categoryName) {
this.category = categoryName;
return "Esta es la: " + this.category;
}
window.onload = function () {
var cancionalegre = new Array();
cancionalegre[0] = new Tune("La culebra", "Jose Domingo");
// Imprime la cancion y el artista
alert(cancionalegre[0].concat());
alert(cancionalegre[0].addCategory("categoria de ejemplo"));
}
</script>
</body>
</html>
Haz clic aquí para observar el resultado.
Cada objeto en JavaScript hereda de Object, y todos los métodos y otras propiedades se heredan a través del objeto prototipo. Es a través del prototipo de objeto que podemos extender cualquier objeto, y eso incluye los objetos incorporados, como Cadena y Número.
Una vez que un objeto se ha extendido a través de la propiedad del prototipo, todas las instancias del objeto dentro del alcance de la aplicación tienen esta funcionalidad.
<!DOCTYPE html>
<html>
<head>
<titulo>Melodia Object</titulo>
<script>
function Melodia(cancion,artista) {
var titulo = cancion;
var artista = artista;
this.concat = function() {
return titulo + " " + artista;
}
}
window.onload=function() {
// Creacion de una instancia e impresión de valores
var happycancion = new Melodia("Que feliz soy", "Juan Perez");
// extendiende el objeto
Melodia.prototype.addCategory = function(categoryName) {
this.category = categoryName;
}
// agrega categoria
happycancion.addCategory("Cumbia");
// print cancion out to new paragraph
var cancion = "titulo y artista: " + happycancion.concat() +
" Categoria: " + happycancion.category;
var p = document.createElement("p");
var txt = document.createTextNode(cancion);
p.appendChild(txt);
document.getElementById("cancion").appendChild(p);
}
</script>
</head>
<body>
<h1>Melodia</h1>
<div id="cancion">
</div>
</body>
</html>
Haz clic aquí para observar el resultado.
En el ejemplo anterior, el nuevo objeto, “Melodía”, se define utilizando la sintaxis de la función. Tiene dos miembros de datos privados, un título y un artista. Un método de acceso público, “concat”, toma estos dos miembros de datos privados, los concatena y devuelve el resultado.
Después de que se crea una nueva instancia del objeto, y el objeto se amplía con un nuevo método y miembro de datos, el nuevo método se usa para actualizar la instancia del objeto existente.
La propiedad prototipo también se puede usar para anular o extender objetos externos o globales.
Agregar getter/setter a los objetos
Supón que deseas proporcionar acceso de propiedad a datos protegidos.
Utiliza la funcionalidad getter/setter:
<!DOCTYPE html>
<html>
<head>
<titulo>Melodia Object</titulo>
<script>
function Melodia() {
var artista;
var cancion;
this.__defineGetter__("artista", function () {
return artista
});
this.__defineSetter__("artista", function (val) {
artista = "Por: " + val
});
this.__defineGetter__("cancion", function () {
return "cancion: " + cancion
});
this.__defineSetter__("cancion", function (val) {
cancion = val
});
}
window.onload = function () {
var happycancion = new Melodia();
happycancion.artista = "Juan Perez";
happycancion.cancion = "Cancion de felicidad";
alert(happycancion.cancion + " " + happycancion.artista);
}
</script>
</head>
<body>
</body>
</html>
Haz clic aquí para observar el resultado.
Podemos agregar funciones a nuestros objetos para obtener datos privados, como se muestra en el ejemplo anterior. Sin embargo, cuando usamos las funciones, son funciones obvias. La funcionalidad getter/setter es una sintaxis especial que proporciona acceso similar a propiedades a los miembros de datos privados. Las funciones getter y setter proporcionan una capa adicional de protección para los miembros de datos privados. Las funciones también nos permiten preparar los datos, como se demostró con la solución, donde las cadenas de canciones y artistas se concatenan a las etiquetas.
Heredar la funcionalidad de un objeto
Supón que al crear un nuevo tipo de objeto deseas heredar la funcionalidad de un
objeto JavaScript.
Utiliza el concepto de encadenamiento de constructores y el método Function.apply para emular el comportamiento tradicional de herencia de clases en JavaScript:
<!DOCTYPE html>
<html>
<head>
<titulo>Melodia Object</titulo>
<script>
function objetoAnterior(param1) {
this.param1 = param1;
this.getParam = function () {
return this.param1;
}
}
function objetoNuevo(param1, param2) {
this.param2 = param2;
this.getParam2 = function () {
return this.param2;
}
objetoAnterior.apply(this, arguments);
this.getAllParameters = function () {
return this.getParam() + " " + this.getParam2();
}
}
window.onload = function () {
objetoNuevo.prototype = new objetoAnterior();
var obj = new objetoNuevo("valor1", "valor2");
// prints out both parameters
alert(obj.getAllParameters());
}
</script>
</head>
<body>
</body>
</html>
Haz clic aquí para observar el resultado.
En la solución tenemos dos objetos: el original que es objetoAnterior y objetoNuevo que hereda la funcionalidad del objeto original. Para que esto funcione, un par de cosas están sucediendo.
Primero, en el constructor de objetoNuevo se invoca un método “apply” en “objetoAnterior”, pasando una referencia al nuevo objeto y el argumento array. El método apply se hereda del objeto “Function” y toma como referencia el objeto que llama o el objeto window si el valor es nulo, y una matriz opcional de argumentos.
La segunda parte de la herencia es la línea:
objetoNuevo.prototype = new objetoAnterior();
Este es un ejemplo de encadenamiento de constructor en JavaScript. Lo que sucede es que cuando creas una nueva instancia de objetoNuevo, también creas una nueva instancia de objetoAnterior, de tal forma que objetoNuevo hereda los métodos y la propiedad del objeto anterior.
Es esta combinación de encadenamiento de constructor, que encadena a los constructores de los objetos nuevos, y el método de aplicación, que transfiere el contexto del objeto y los datos al objeto heredado, se implementa el comportamiento de herencia en JavaScript. Debido a esta herencia, el nuevo objeto tiene acceso no sólo a su propiedad, param2 y método, getParam2, sino que también tiene acceso a la propiedad y el método param1 y getParam del objeto anterior.
Para ver otro ejemplo de herencia de JavaScript, el siguiente muestra cómo funciona con otro par de objetos: un objeto Libro y un objeto TechBook, que hereda de Libro.
<!DOCTYPE html>
<html>
<head>
<Titulo>Encadenamiento de constructor</Titulo>
<script>
function Libro(Titulo, Autor) {
var Titulo = Titulo;
var Autor = Autor;
this.getTitulo=function() {
return "Titulo: " + Titulo;
}
this.getAutor=function() {
return "Autor: " + Autor;
}
}
function TechLibro (Titulo, Autor, categoria) {
var categoria = categoria;
this.getcategoria = function() {
return "Categoria: " + categoria;
}
Libro.apply(this,arguments);
this.getLibro=function() {
return this.getTitulo() + " " + Autor + " " + this.getcategoria();
}
}
window.onload=function() {
// Encadena los constructores de objeto
TechLibro.prototype = new Libro();
// Muestra todas los valores
var nuevoLibro = new TechLibro("Libro de cocina", "Jose García", "Cocina");
alert(nuevoLibro.getLibro());
// Ahora de forma individual
alert(nuevoLibro.getTitulo());
alert(nuevoLibro.getAutor());
alert(nuevoLibro.getcategoria());
}
</script>
</head>
<body>
<p>Algo de texto</p>
</body>
</html>
Haz clic aquí para observar el resultado.
A diferencia de los objetos en la solución, todos los elementos de datos en ambos objetos en el anterior están protegidos, lo que significa que no se puede acceder directamente a los datos fuera de los objetos. Sin embargo, observa cuando las tres propiedades del libro -título, autor y categoría- se imprimen mediante el método getLibro en el objeto “TechLibro”, que “TechLibro” tiene acceso al autor del libro y a las propiedades del título, además de su categoría propia. Esto se debe a que cuando se creó el nuevo objeto “ TechLibro”, también se creó un nuevo objeto. Para completar la herencia, los datos utilizados en el constructor de “TechLibro” se pasan a través del objeto “libro” que utiliza el método “apply”.
El objeto “ TechLibro“ no sólo puede acceder directamente a los datos de la instancia de Book, sino que también puede acceder a los métodos del objeto Book, getAutor y getCategoria , directamente en la instancia del objeto “TechLibro“ instanciado.
Extender un objeto al definir una nueva propiedad
Supón que deseas extender un objeto existente agregando una nueva propiedad, pero sin cambiar la función de constructor del objeto.
Utiliza el método “Object.defineProperties” para definir una propiedad:
Object.defineProperty(nuevoLibro, "editor", { value: "Editorial Rana", writable: false, enumerable: true, configurable: true});
Usa el método Object.defineProperties para definir más de una propiedad:
Object.defineProperties(nuevoLibro, {
"existencia": {
value: true,
writable: true,
enumerable: true,
},
"antiguedad": {
value: "13 o mas años",
writable: false
}
});
El control se produce a través de la provisión de varios atributos que se puede asignar a una propiedad cuando se crea. Estos atributos forman lo que se conoce como el objeto descriptor de propiedad e incluyen:
Haz clic en cada elemento.
Si es verdadero, la propiedad puede ser cambiada; de otra forma no.
Si es verdadero, la propiedad se puede eliminar o cambiar; de otra forma no.
Si es verdadero, la propiedad se puede iterar.
El tipo de descriptor de propiedad también puede variar. Si el descriptor es un descriptor de datos, otro atributo es el valor, demostrado en la solución.
Previniendo la extensibilidad del objeto
Supón que deseas evitar que otros desarrolladores extiendan un objeto.
Utiliza el método Object.preventExtensions para bloquear un objeto contra futuras incorporaciones de propiedad:
"use strict";
var Test = {
value1 : "one",
value2 : function() {
return this.value1;
}
};
try {
Object.preventExtensions(Test);
// lo siguiente falla y arroja una excepción en modo estricto
Test.value3 = "test";
} catch(e) {
alert(e);
}
El método Object.preventExtensions evita que los desarrolladores amplíen el objeto con nuevas propiedades, aunque los valores de propiedad en sí mismos todavía se pueden escribir.
Enumeración de las propiedades de un objeto
Supón que deseas ver qué propiedades tiene un objeto.
Usa una variación especializada del ciclo for para iterar a través de las propiedades:
Ejemplo:
for (var prop in obj) {
alert(prop); // imprime el nombre de la propiedad
}
Objetos y nombres únicos espaciados de JavaScript
Supón que deseas encapsular la funcionalidad de tu biblioteca de tal forma que evite conflictos con otras bibliotecas.
Usa un objeto literal, lo que se llama un objeto único, para implementar la versión de JavaScript del espacio de nombres. Un ejemplo es el siguiente:
<!DOCTYPE html>
<html>
<head>
<Titulo></Titulo>
<script>
var jscbObject = {
// return element
getElem : function (identifier) {
return document.getElementById(identifier);
},
stripslashes: function (str) {
return str.replace(/\\/g, '');
},
removeAngleBrackets: function (str) {
return str.replace(/</g, '<').replace(/>/g, '>');
}
};
var incoming = jscbObject.getElem("incoming");
var content = incoming.innerHTML;
var result = jscbObject.stripslashes(content);
result = jscbObject.removeAngleBrackets(result);
jscbObject.getElem("result").innerHTML = result;
</script>
</head>
<body>
</body>
</html>
Haz clic aquí para observar el resultado.
Al usar un literal de objeto puedes ajustar toda la funcionalidad de tu biblioteca, de tal forma que las funciones y variables que necesitas no se encuentren en el espacio global. El único objeto global es el literal del objeto real, y si usamos un nombre que incorpore la funcionalidad, el grupo, el propósito, el autor, etc., de una manera única, usamos el espacio de nombres eficazmente para evitar conflictos de nombres con otras bibliotecas.
Cierre
En este tema aprendiste los fundamentos de la programación orientada a objetos, la cual, en el caso de JavaScript, está basada en prototipos. Esto te permitirá comprender algunos temas avanzados de HTML5, los cuales se estudiarán en los siguientes temas.
Checkpoint
Asegúrate de:
Referencias