NF1 - Estructures definides pel Programador - Objectes
Contingut
Objectes
Instanciació d'objectes
La forma més sencilla de crear un nou objecte és amb una intrucció com aquesta :
var obj1 = new Object();
var obj2 = {};
Això crea un objecte nou, buit i que més tard podem ampliar amb propietats:
var o = {};
o.name = "Marta";
o.feina = "contable";
o.antiguitat = 11;
Per eliminar una propietat podem utilitzar l'operador delete:
var obj1 = new Object()
alert(obj1.nombre) // undefined
obj1.nombre = "mundo"
alert(obj1.nombre) // "mundo"
delete obj1.nombre
alert(obj1.nombre) // undefined otra vez
Per afegir un mètode podem fer:
var obj1 = new Object();
obj1.nombre = "mundo";
obj1.saluda = function() { alert("hola "+this.nombre) };
obj1.saluda();
També podem construir objectes de manera literal:
var obj1 = {nombre:"mundo",
saluda: function() { alert("hola "+this.nombre) }
};
obj1.saluda();
I podem accedir als mètodes i a les propietats de diferents maneres:
var obj1 = new Object();
obj1.nombre = "mundo";
obj1.saluda = function() { alert("hola "+this.nombre) };
alert("hola " + obj1.nombre); // Acceso normal
alert("hola " + obj1[ "nombre" ]); // Acceso indexado con un literal
var prop = "nombre";
alert("hola " + obj1[ prop ]); // Acceso indexado con una variable
obj1.saluda(); // Invocación normal
obj1[ "saluda" ](); // Acceso indexado con un literal
var func = "saluda";
obj1[ func ](); // Acceso indexado con una variable
Mètodes privats Aquesta part ja s'ha vist en el capitol de les funcions, però no està de més tornar a fer una ullada:
/*CREACIÓ CLASSES ECMASCRIP 5*/
/*Ejemplo 1*/
var Saludator = function(nom) {
this.nombre = nom
this.saluda = function() {
alert("hola "+plural(this.nombre))
}
function plural(n) {
return n + "s"
}
}
var obj1 = new Saludator("mundo")
obj1.saluda()
// obj1.plural() // fallaría porque plural() es privado
/*Ejemplo 2*/
function Animal(nombre, especie, numerodepatas, tienecola) {
//Variables privadas el guión "_" no significa que haga las variables privadas sino que por convenio se especifica
var _nombreCompleto;
var _color;
// Propiedades
this.Nombre = nombre;
this.Especie = especie;
this.NumeroDePatas = numerodepatas;
this.TieneCola = tienecola;
// Metodos
this.setColor = function (color) {
_color = color;
}
this.getColor = function () {
return _color;
}
this.setNombreCompleto = function() {
_nombreCompleto = "Este Animal es un " + this.Nombre + " de la especie de los " + this.Especie;
}
this.getNombreCompleto = function() {
this.setNombreCompleto();
return _nombreCompleto;
}
}
Els que venim d'un entorn orientat a objectes clàssic trobaran a faltar la encapsulació i la estructuració que implica el concepte de constructor de classe. Javascript proporciona aquest mecanisme però d'una manera molt diferent. En Javascript totes les funcions i els objectes tenen una propietat anomenada prototype que fa referència a un objecte buit de forma inicial. Aquest objecte buit es crea utilitzant la paraula new.
CREACIÓ CLASSES ECMASCRIPT 6
MÉTODOS ESTÁTICOS
En algunos casos, nos puede interesar crear métodos estáticos en una clase porque para utilizarlos no hace falta crear ese objeto, sino que se pueden ejecutar directamente sobre la clase directamente.
Una de las limitaciones de los métodos estáticos es que en su interior sólo podremos hacer referencia a elementos que también sean estáticos.
No podremos acceder a propiedades o métodos no estáticos, ya que necesitaríamos instanciar un objeto para hacerlo.
Los métodos estáticos se suelen utilizar para crear funciones de apoyo que realicen tareas concretas o genéricas, porque están relacionadas con la clase en general.
class Animal {
static despedirse() {
return "Adiós";
}
hablar() {
return "Cuak";
}
}
Animal.despedirse(); // 'Adiós'
Las clases, siendo estructuras para guardar información, pueden guardar variables con su correspondiente información. Dicho concepto se denomina propiedades y en Javascript se realiza en el interior del constructor, precedido de la palabra clave this (que hace referencia a «este» elemento, es decir, la clase).
class Animal {
constructor(n = "pato") {
this.nombre = n;
}
hablar() {
return "Cuak";
}
quienSoy() {
return "Hola, soy " + this.nombre;
}
}
// Creación de objetos
const pato = new Animal();
pato.quienSoy(); // 'Hola, soy pato'
const donald = new Animal("Donald");
donald.quienSoy(); // 'Hola, soy Donald'
/*las propiedades de la clase podrán ser modificadas externamente, ya que por defecto son propiedades públicas*/
pato.nombre = "Paco";
pato.quienSoy(); // 'Hola, soy Paco'
Propiedades y métodos privados
A partir de la versión ECMAScript 2020, se introduce la posibilidad de crear campos de clase privados (los cuales aún cuentan con poco soporte).
Antiguamente todas las propiedades y métodos eran públicos por defecto, pero ahora también pueden ser privados. Para ello, solo hay que añadir el carácter # justo antes del nombre de la propiedad o método.
Estas propiedades o métodos precedidos de # son privados y sólo podrán ser llamados desde un método de clase, ya que si se hace desde fuera obtendremos un error.
Sin embargo, si se llama a un método público como decirSacrilegio(), que a su vez llama a un método privado (pero desde dentro de la clase), todo funcionará correctamente sin error, ya que el método #decirSecreto() se está llamando desde dentro de la clase.
class Animal {
#miSecreto = "Me gusta Internet Explorer";
#decirSecreto() {
return this.#miSecreto;
}
decirSacrilegio() {
return this.#decirSecreto();
}
}
const patitoFeo = new Animal();
patitoFeo.#decirSecreto(); // Error
patitoFeo.decirSacrilegio(); // OK
Consulta la compatibilitat de mètodes privats a: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields
Ámbitos de en una clase
Dentro de una clase tenemos dos tipos de ámbitos: ámbito de método y ámbito de clase:
1)En primer lugar, veamos el ámbito dentro de un método. Si declaramos variables o funciones dentro de un método con var, let o const, estos elementos existirán sólo en el método en cuestión. Además, no serán accesibles desde fuera del método.
Observa que la variable name solo se muestra cuando se hace referencia a ella dentro del constructor() que es donde se creó y donde existe
class Clase {
constructor() {
const name = "Manz";
console.log("Constructor: " + name);
}
metodo() {
console.log("Método: " + name);
}
}
const c = new Clase(); // 'Constructor: Manz'
c.name; // undefined
c.metodo(); // 'Método: '
2) En segundo lugar, tenemos el ámbito de clase. Podemos crear propiedades precedidas por this. (desde dentro del constructor) y desde ES2020 desde la parte superior de la clase, lo que significa que estas propiedades tendrán alcance en toda la clase, tanto desde el constructor, como desde otros métodos del mismo.
Ojo, estas propiedades también pueden ser modificadas desde fuera de la clase, simplemente asignándole otro valor. Si quieres evitarlo, añade el # antes del nombre de la propiedad al declararla (ejemplo #role).
class Clase {
role = "Teacher"; // ES2020+
constructor() {
this.name = "Manz";
console.log("Constructor: " + this.name);
}
metodo() {
console.log("Método: " + this.name + this.role);
}
}
const c = new Clase(); // 'Constructor: Manz'
c.name; // 'Manz'
c.metodo(); // 'Método: Manz'
c.role; // 'Teacher'
La palabra clave this
Como te habrás fijado en ejemplos anteriores, hemos introducido la palabra clave this, que hace referencia al elemento padre que la contiene.
Así pues, si escribimos this.nombre dentro de un método, estaremos haciendo referencia a la propiedad nombre que existe dentro de ese objeto.
De la misma forma, si escribimos this.hablar() estaremos ejecutando el método hablar() de ese objeto.
Ten en cuenta que si usas this en contextos concretos, como por ejemplo fuera de una clase te devolverá el objeto Window, que no es más que una referencia al objeto global de la pestaña actual donde nos encontramos y tenemos cargada la página web.
class Animal {
constructor(n = "pato") {
this.nombre = n;
}
hablar() {
return "Cuak";
}
quienSoy() {
return "Hola, soy " + this.nombre + ". ~" + this.hablar();
}
}
const pato = new Animal("Donald");
pato.quienSoy(); // 'Hola, soy Donald. ~Cuak'
EJEMPLO CLASE CON GETTERS y SETTERS
// Para crear una clase se usa la palabra reservada 'class'
class Persona {
/* El método 'constructor' se utiliza para inicializar los atributos
* de la clase.
*
* Observar que se pueden especificar valores por defecto a los perámetros.
* - Se pasa el valor por defecto 'conocido' a 'tipoSaludo'.
*/
constructor(nombre, edad, email, tipoSaludo = 'conocido') {
// Para hacer referencia a las propiedades del objeto se utiliza la
// palabra reservada 'this'.
this._nombre = nombre;
this._edad = edad;
this._email = email;
this._tipoSaludo = tipoSaludo;
}
// Se pueden crear los getter con la palabra reservada 'get'.
// Los getter sirven para obtener los valores de las propiedades
// del objeto.
get nombre() {
return this._nombre;
}
// Se pueden crear los setter con la palabra reservada 'set'.
// Los setter sirven para asignar nuevos valores a las propiedades
// del objeto.
set tipoSaludo(tipoSaludo) {
this._tipoSaludo = tipoSaludo;
}
// Para crear un método simplemente se define su nombre
saludar(nombre) {
if (this._tipoSaludo === 'conocido')
console.log(`Hola ${nombre}, ¿Cómo estas?`);
else
console.log(`Hola, mi nombre es ${this._nombre}`);
}
/* En algunas ocaciones se puede dar el caso de que no podemos tener
* acceso a nuestro objeto, la solución a este inconveniente se muestra
* y explica en este método.
*/
mostrarme() {
// Declarando una variable local y asignándole una referencia al propio
// objeto.
let _this = this;
// En una función anónima no se puede acceder al propio objeto usando
// la palabra reservada 'this' (obtenemos como salida 'undefined').
(function () {
console.log(this);
})();
// Una solución es declarar una variable y asignarle una referencia
// al objeto como se hace al inicio del método.
(function () {
console.log(_this);
})();
// Esta es la manera correcta y elegante de acceder a nuestro objeto.
((e) => {
console.log(this);
})();
}
// Los métodos estáticos se declaran usando la palabra reservada 'static'.
static girar() {
console.log('Girando!');
}
}
// Para crear una instancia de la clase 'Persona' se usa la
// palabra reservada 'new'. Recordar que el cuarto parámetro
//es opcional, por lo que al no pasarle valor tomara por
//defecto el especificado en el método 'constructor' de la clase.
var p = new Persona('Julio', 30, 'juliolamerce@gmail.com');
// Llamando a uno de sus métodos.
p.saludar('Ana');
// Cambiando el valor del atributo 'tipoSaludo' usando el setter tipoSaludo
p.tipoSaludo = 'otro';
p.saludar();
// Obtenieno el valor del atributo 'nombre' usando el getter nombre
console.log(p.nombre);
// Ejemplo del acceso al propio objeto y la mejor forma de hacerlo, en
// circunstancias como: los eventos, funciones anónimas, uso de JQuery dentro
// del método, etc.
p.mostrarme();
// Un método estático no necesita de una instacia de clase para ser invocado.
Persona.girar();
https://lenguajejs.com/javascript/caracteristicas/clases-es6/
Herència
ECMAScript 6
class Persona {
// constructor donde definir las variables que se reciben y guardarlas en el objeto usando this
constructor(nombre, edad) {
this.nombre = nombre;
this.edad = edad;
}
// método para presentarse
presentarse() {
return 'Hola me llamo ' + this.nombre + ' y tengo ' + this.edad + ' años';
}
get verNombre() {
return this.nombre;
}
set nuevoNombre(nuevo) {
this.nombre = nuevo;
}
}
//var persona = new Persona('Julio', 22);
//persona.presentarse();
class Desarrollador extends Persona {
constructor(nombre, edad, cargo) {
super(nombre, edad);
this.cargo = cargo;
}
presentarse() {
return super.presentarse() + ' y soy desarrollador ' + this.cargo;
}
}
var persona = new Desarrollador('Julio', 22, 'Frontend');
persona.presentarse(); // 'Hola me llamo Julio y tengo 22 años y soy desarrollador Frontend'
https://lenguajejs.com/javascript/caracteristicas/herencia-de-clases/
https://platzi.com/blog/ecmascript-nueva-sintaxis/
https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Classes
ECMAScript 5 Herència amb prototips
Analitzarem l'us de la paraula new a una funció i veurem com la propietat prototype proporciona propietats per a la nova instància.
function Persona(){}
Persona.prototype.caminar = function(){return true;};
var pere = Persona();
assert(pere === undefined, "No s'ha creat cap instancia de Persona");
var jaume = new Persona();
assert(jaume && jaume.caminar && jaume.caminar(), "La instancia existeix i el mètode es pot cridar");
Podem veure en l'exemple anterior que pere te el valor undefined. Aixó és així per que la funció Persona, si no es crida amb el constructor new no retorna cap valor. Veiem que és veritat, ja que quan el cridem amb el constructor new Jaume existeix i a més a més té un mètode anomenat caminar.
Llavors, què és un prototip??
Un prototype és un objecte del que altres objectes hereten propietats.Aquest objecte pot ser qualsevol objecte. L'herència basada en classes no existeix en Javascript, sinó que és herència basada en prototips. Això significa que no hi ha una jerarquia de classes real (amb la seva extends, accés super, etc.), sinó que cada classe té un objecte de referència (com si fos la seva classe pare) on consultar atributs i mètodes en cas que la nostra classe no els trobi. Podem intentar simular una jerarquia (ull, simular) usant aquests objectes de referència, anomenats prototips, amb una propietat especial que tenen totes les “classes” anomenada prototype. Aquesta propietat és asignable i la seva labor consisteix a guardar un objecte. Vegem-ho amb un exemple:
var Saludator = function(nom) {
this.nombre = nom
this.saluda = function() {
alert("hola "+this.nombre)
}
}
// Añade un objeto al prototipo
Saludator.prototype = {
apellido: "cruel",
despide: function() {
alert("adios "+this.nombre+" "+this.apellido)
}
}
var obj1 = new Saludator("mundo")
obj1.despide() // "adios mundo cruel"
Quins objectes tenen prototip?
Tot objecte té un prototip per defecte. Com els prototips són objectes, tots ells tenen un prototip també. Solament hi ha una excepció: el prototip de l'objecte per defecte, al final de la cadena de prototips (Object).
Totes les classes tenen per defecte un objecte prototype buit, com si en crear-les es fes prototype = {}. Per aquesta raó, podem accedir a aquesta variable i assignar-li els atributs d'un en un. El següent exemple és completament equivalent a l'anterior:
var Saludator = function(nom) {
this.nombre = nom
}
// Añade nuevas propiedades al prototipo
Saludator.prototype.apellido = "cruel"
Saludator.prototype.despide = function() {
alert("adios "+this.nombre+" "+this.apellido)
}
var obj1 = new Saludator("mundo")
obj1.despide() // "adios mundo cruel"
Ok, recapitulem… què deies que era un objecte?
Un objecte en Javascript és una col·lecció qualsevol de parells clau-valor. Si no és un valor primitiu (undefined, null, boolean, number o string) és un objecte.
Utilitzant Prototips
Estudia aquest codi:
function Persona(){
this.parat = false;
this.caminar = function(){return !this.parat;};
}
Persona.prototype.caminar = function(){return this.parat;};
var joan = new Persona();
console.log(joan.caminar()); //La crida a caminar és la crida definida dintre de Persona i no al mètode del prototip"
Això demostra que el membres de la instància creats dintre del constructor bloquejaran les propietats i els mètodes definits amb el mateix nom en el prototip.
Com abordar les referències als mètodes i propietats dels prototips
Amb els exemples vistos anteriorment, pots arribar a pensar que quan es crea un nou objecte es copien els valors i els mètodes del seu prototip, i si hi ha algún mètode o propietat que s'anomena igual, es sobreescriu el seu valor amb el valor que hi ha en el constructor de l'objecte. Si fos així, en el següent codi, el mètode caminar no es podria utilitzar :
function Persona(){
this.parat = true;
}
var joan = new Persona();
Persona.prototype.caminar = function(){return this.parat;};
assert(joan.caminar(), "El mètode existeix, fins i tot quan la persona s'ha creat abans.");
Llavors, aixó demostra que no hi ha una copia de propietats si no molt més. El que succeeix es que les funcions i mètodes del prototip s'adjunten a l'objecte construït i es consulta durant la conciliació de les referències de la propietat que es duen a terme en l'objecte. És a dir:
- 1. Quan es fa una referència de propietat a un objecte, es comprova a veure si existeix en el propi objecte, si existeix s'agafa el valor, si no...
- 2. Es localitza el prototip associat al objecte i es comprova si existeix la propietat, Si existeix es retorna el valor, si no...
- 3. El valor es undefined
Llavors podem dir que els mètodes i les propietats NO ES COPIEN, S'ADJUNTEN de forma activa al objecte.
Funció constructor d'un objecte
Tots els objectes tenen una referència a la funció que s'ha utilitzat per construir l'objecte. Aquesta propietat s'anomena constructor. Exemple:
function Persona(){
this.parat = true;
}
var joan = new Persona();
Persona.prototype.caminar = function(){return this.parat;};
En aquest cas joan.constructor fa referència a la funció Persona(). Sabem que les funcion son objectes, per tant, aquesta funció té propietats. Una de elles és la propietat prototype. Per tant, podem accedir a les propietats i mètodes del prototype des del mateix objecte: joan.constructor.prototype.caminar.
Herència amb Prototips
Volem crear hereència entre diferents objectes. Analitza el següent codi:
function Persona(){}
Persona.prototype.ballar = function(){alert("eeeeo");};
function Alumne(){}
Alumne.prototype = { ballar : Persona.prototype.ballar}; //fa refència a ballar del mètode de Persona
var joan = new Alumne();
joan.ballar();
console.log(joan instanceof Alumne); //true
console.log(joan instanceof Persona); //false
console.log(joan instanceof Object); //true
Creus que està ben aconseguida la herència? Prova-ho. D'aquesta manera estem copiant la funció ballar definida en Persona en Alumne. Però no estem creant herència, estem copiant funcions. Llavors quina tècnica s'utilitza??
El que volem crear és una cadena de prototips per tal que Alumne pugui ser una Persona i aquesta a la seva vegada un Mamífer i aquesta una Animal i així fins arribar a Object. La millor manera de fer-ho és utilitzant una instància d'objecte com a prototip.
Subclass.prototype = new SuperClass();
Exemple:
Alumne.prototype = new Persona();
L'exemple anterior quedaria de la següent manera:
function Persona(){}
Persona.prototype.ballar = function(){alert("eeeeo");};
function Alumne(){}
Alumne.prototype = new Persona();
var joan = new Alumne();
joan.ballar(); //joan apuntaria a Persona i executaria el mètode de ballar de Persona
console.log(joan instanceof Alumne); //true "joan és un alumne"
console.log(joan instanceof Persona); //true "joan és una Persona"
console.log(joan instanceof Object); //true "joan és una Objecte"
Casi casi ho hem aconseguit. L'unic problema es que hem perdut la referència al nostre propi constructor. joan.constructor apunta a Persona en compte d'apuntar a Alumne. Com ho solucionem?? fàcil:
function Persona(){}
Persona.prototype.ballar = function(){alert("eeeeo");};
function Alumne(){}
Alumne.prototype = new Persona();
Alumne.prototype.constructor = Alumne;
var joan = new Alumne();
joan.ballar();
console.log(joan instanceof Alumne); //true
console.log(joan instanceof Persona); //true
console.log(joan instanceof Object); //true
Dibuix amb la cadena de prototips:
Exemple d'herència amb prototips
Exemple de herència :
// define la clase Person
function Person() {}
Person.prototype.walk = function(){};
Person.prototype.sayHello = function(){ alert ('hola');};
// define la clase Student
function Student() {
// Llama al constructor primario
Person.call(this);
}
// hereda Person
Student.prototype = new Person();
// corrige el puntero del constructor porque apunta a Person
Student.prototype.constructor = Student;
// reemplaza el método sayHello
Student.prototype.sayHello = function(){
alert ('hola, soy estudiante');
}
// agrega el método sayGoodBye
Student.prototype.sayGoodBye = function(){
alert ('adiós');
}
var student1 = new Student();
student1.sayHello();
student1.sayGoodBye();
// comprueba la herencia
alert(student1 instanceof Person); // true
alert(student1 instanceof Student); // true
Altre Exemple:
function Padre(attPadre) {
this.atributoPadre=attPadre;
}
function Hijo(attHijo, attPadre) {
Padre.call(this,attPadre);
this.atributoHijo=attHijo;
}
Hijo.prototype = new Padre();
a = new Hijo(2,3);
alert (a.atributoPadre);
alert (a.atributoHijo);
Exercici clase TEXT
Crea una classe TEXT que permeti :
- Definir una cadena de caràcters inicialitzada pel constructor
- Afegir caràcters
- Saber quantes vocals hi ha en el text
- Mostrar el text
Exercici Figures
- Tenemos una clase padre Figura que recibe como parámetro un color, y tienen sus get y set y un método que muestra la info(color).
- La clase Cuadrado Hereda de Figura, recibe color y lado, tiene sus get y set, dos métodos calculaArea y muestra info(color,lado y área).
- La clase Rectángulo hereda de Figura, recibe color, base, altura, tiene sus get y set, dos métodos calcularArea y muestra info(color, base, altura y área).
- La clase Triangulo herede de Rectángulo, recibe color, base, altura, tiene sus get y set, un método que muestra toda la info (color,base, altura y área)
Exercici PROFESSORS
Crea una classe Persona, que contingui les dades nom, cognoms i edat, es podrà obtenir i modificicar els valors. Es haurà de crear una altra classe Professor que baixarà de la classe Persona.
També hi ha una classe ProfessorFixe que heretarà les dades de Professor, a més rebrà com a paràmetre un identificador.
També hi ha una classe ProfessorInteri que heretarà les dades de Professor, a més rebrà com a paràmetre la data que demana l'interinatge.
S'haurà de crear un mètode que mostre les dades de tots els objectes creats. (Nom, cognoms, id, data ...)
S'haurà de crear una classe ListaProfe que contindrà un array on s'aniran emmagatzemant els objectes de ProfeFixe, ProfeInteri i Profes per això hi ha d'haver un mètode insertarProfe i un altre obtenerProfe i per últim mostrarProfes
Insertar 3 profesInteri, 3 profesFixe i 3 Professors.
Solució als Exercicis
Exercici TEXT
<html>
<body>
<h1>
Classe TEXT
</h1>
<script type="text/javascript">
class TEXT {
#text;
constructor(_text){
this.#text=_text;
}
afegir(_text) {
this.#text+=_text;
}
comptaVocals() {
var esVocal=function(c){
return c=="a" || c=="e" || c=="i" || c=="o" || c=="u";
};
var array1=this.#text.split("");
var array2=array1.filter(esVocal);
return array2.length;
}
get text(){
return this.#text;
}
}
var text1=new TEXT("Hola, que tal?");
document.write("<br>text:"+text1.text);
text1.afegir(" bon dia");
document.write("<br>text:"+text1.text);
document.write("<br>num vocals:"+text1.comptaVocals());
</script>
</body>
</html>
Bibliografia
John Resig, Bear Bibeault, "Secrets of the Javascript Ninja", Manning Publications, 2012. ISBN 193398869X
https://www.youtube.com/watch?v=428uJQjcJmg
https://geekytheory.com/prototipado-y-herencia-en-javascript
https://geekytheory.com/contextos-y-encapsulamiento-en-javascript/