duduromeroa.com

#Javascript, #DOMJs, #webinteractiva, #Interfaces, #duduromeroa, #Guayaquil

Javascript

Programación para web (2.1/8): orientación a objetos, objetos, métodos, argumentos y parámetros


George Póyla, matemático. Resolver problemas de diseño. www.duduromeroa.com

Por Eduardo J. Romero Andrade
| Guayaquil




SIMULAR EL MUNDO CON OBJETOS, PROPIEDADES Y VALORES: EL MODELO DE LA PROGRAMACIÓN ORIENTADA A OBJETOS

En nuestra vida lo tangible tiene propiedades táctiles y visualizables; pero en sistemas informáticos las propiedades tienen valores. En nuestra vida un objeto físico tiene propiedades como textura, peso, color, como una manzana. Y esta podría ser alterada por acción nuestra; por ejemplo, los valores de la propiedad 'forma' cambiará si la cortamos a la mitad.

Aristóteles y la programación orientada a objetos

publicado en el 2003 explora las relaciones entre los conceptos de la programación orientada a objetos y la causalidad aristotélica; en la cual "las entidades –acciones o métodos– actúan y modelan el estado de una realidad –una clase–", y viceversa. El artículo está en inglés.



En lenguajes para cómputo un objeto tiene propiedades con valores alterables, intercambiables y ampliables; pero ese objeto será solo una simulación.

En JS un objeto es armado con la siguiente sintaxis:

var objeto = {
// Propiedades: 'valores'
propiedad1: 'valorA', 
propiedad2: 'valorB'
};
// Invocar valor de propiedad
console.log(objeto.prop1);

Y dos objetos –uno dentro de otro– con la siguiente sintaxis. OJO con la coma luego de la última propiedad en el primer objeto:

En Javascript

var objeto = {
// Propiedades: 'valores'
prop0: 'valorA', 
prop1: 'valorB',

objAnidado: {
prop2: 'valorC', 
prop3: 'valorD'
}};
// Invocar valor de objeto anidado
console.log(objeto.objAnidado.prop2);
//-> valorC

Luego con ejemplos:

// Objeto manzana
var manzana = {
// Propiedades con sus valores
color: 'rojo', 
forma: "redonda", 
tipo: "fruta",
 
// Objeto anidado con propiedades y sus valores
origen: {
chilena: true, 
española: false, 
ecuatoriana: false
}};

// Invocando propiedades y valores
// "..." + nombreObjeto.propiedad
console.log("La manzana tiene color " + 
manzana.color);

//-> La manzana tiene color rojo

Según Antani y Stefanov[4], una analogía literaria con el modelo de programación orientada a objetos sería la siguiente:

  • Un sustantivo –como una manzana– sería el objeto a modelar.
  • Los verbos –como las acciones a ejecutar– serían los métodos.
  • Los adjetivos –color: "rojo", velocidad: 200- serían las propiedades y sus valores, respectivamente.
CLASES Y OBJETOS: INTRODUCCIÓN

Para Antani y Stefanov[4], en lenguajes de programación los "objetos similares se agrupan en clases", donde "una clase –padre– es una plantilla para crear –o instanciar– nuevos objetos" hijos.

Cada nuevo objeto es creado en una instancia –del neologismo instance–, que refiere a un 'ejemplar' o 'espécimen'. Una clase padre ahorra trabajo de instancia porque permite crear varios objetos hijos a partir de un solo conjunto de propiedades; pero con valores que podrían ser diferentes, evitando así duplicar propiedades por cada objeto a instanciar.

Javascript no es un lenguaje puro del modelo de clases. ¿Por qué se dice eso?: porque JS no muestra una sintaxis más elaborada –o más compleja– para crear objetos hijo desde clases, como sí lo tienen otros lenguajes, como C++. Otros autores opinan que la importancia no está en las clases, y sí en los objetos, que son los verdaderos representantes digitales del mundo real. Para una clase no contiene los valores, solo sus objetos. Estos son los que dan la información para las instancias. Luego, Benčević y Antani y Stefanov[4, p.16] proponen: en lenguajes orientados a objetos, "ellos [los objetos] son las estrellas del show, no las clases".

CONCEPTOS DE LA PROGRAMACIÓN ORIENTADA A OBJETOS

ENCAPSULACIÓN

Encapsular significa que en JS un objeto protege de accesos externos a las propiedades y valores dentro de bloques de código llamados funciones. Es decir, un objeto protege sus datos de cualquier otro evento que no esté contenido en él.

En los ejemplos siguientes la encapsulación ocurre cuando los datos de las variables se 'enganchan' a funciones que ejecutan acciones. Palabas clave como return y this permiten acceder a los datos de las clases y leerlos.

    Función constructor()
  • Cuando es invocado con el operador 'new' crea e inicializa -da valores iniciales– a objetos hijo derivados de una clase o función padre.
  • Un constructor puede ser también una función
    clave this.
  • Esta clave apunta a la clase padre o función constructora o al contexto en el que fueron creadas una o más propiedades –en forma de variables y valores–. La clave this. también prepara esas propiedades para conectarlas al nuevo objeto hijo creado.
  • /* EJEMPLO DE CONSTRUCTOR y CLAVE THIS. */
    // Función NuevoHumano en forma de constructor
    function NuevoHumano(nombre, edad) {
    // Nombres de propiedades y valores
    // Apuntados mediante clave this.
      this.nombre = nombre;
      this.edad = edad;
    }
    
    // Variable guarda instancia
    // 'new' genera objeto desde función constructora
    // Propiedades de clase con nuevos valores
    var instancia1 = new NuevoHumano("Dudu", 20);
    var instancia2 = new NuevoHumano("Sandra", 30);
    
    // Objeto nuevo
    console.log("Nombre: " + instancia1.nombre + 
    " - Edad: " + instancia1.edad);
    
    // Objeto nuevo
    console.log("Nombre: " + instancia2.nombre + 
    " - Edad: " + instancia2.edad);
    
    /* Nombre: Dudu - Edad: 20
    Nombre: Sandra - Edad: 30
    */
    

Recordar: Es un standart que todos los nombres identificadores de las clases inicien con mayúscula. Abajo, otro ejemplo con más uso del constructor, la clave this y el operador new:

En Javascript

// Se crea una clase
class Nombrar {  
/* función constructor() define variable local
que será enviada al nuevo objeto instanciado cuando 
se lo invoque mediante 'new' */
      constructor(){ var name; }  

/* Nuevo método: Apunta al contexto con la variable 
name para dársela al nuevo objeto creado por 'new' */
      obtenerNombre(){ return this.name; }
  
/* Nuevo método: Apunta al contexto con la variable name
como argumento y reinicializa esa variable */
      definirNombre(name){ this.name=name; }  
}  

/* En variable persona se guarda 
nuevo objeto personaent() */
var persona1= new Nombrar();
var persona2= new Nombrar();

// NUEVO OBJETO
persona1.definirNombre("Dudu");  
persona2.definirNombre("Xavier");  

// Se invocan 
console.log(persona1.obtenerNombre() + 
", "+ persona2.obtenerNombre() ); 
//-> Dudu, Xavier

Abajo, otro ejemplo:

// Clase padre Circulo
class Circulo {
// Constructor con parámetro 'radio'
  constructor(radio) {

/* this. apunta a este valor que se inicializa 
con el valor que se le de como argumento. 
Asi mismo, esta propiedad apuntada formará parte de 
cada nuevo objeto instanciado desde clase Circulo */
    this.radio = radio;
  }

/* Método de clase: retorna una operación 
y apunta a la propiedad radio: */
  calculoArea() {
    return Math.PI * this.radio * this.radio;
  }

/* Nuevo método de clase: retorna otra operación 
y apunta a la propiedad radio: */
  calcularPerimetro() {
    return 2 * Math.PI * this.radio;
  }
}

// Se crea variable de contexto para nuevo objeto
var Circulo1 = new Circulo(5);
// Se crea variable de contexto para nuevo objeto
var Circulo2 = new Circulo(10);

// Se invoca variables de contexto de nuevo objeto
// Se invoca el método calculoArea()
console.log("Circulo 1 area:", 
Circulo1.calculoArea());

console.log("Circulo 2 area:", 
Circulo2.calculoArea());

// Se invoca el método calcularPerimetro()
console.log("Circulo 1 perimeter:", 
Circulo1.calcularPerimetro());

console.log("Circulo 2 perimeter:", 
Circulo2.calcularPerimetro());

/*-> Circulo 1 area: 78.53981633974483
Circulo 2 area: 314.1592653589793
Circulo 1 perimeter: 31.41592653589793
Circulo 2 perimeter: 62.83185307179586
*/

LA IMPORTANCIA DE LA FUNCIÓN CONSTRUCTOR() EN FUNCIONES CLASE

Recordar: la función constructor() es la primera que dirá ¡presente! cuando cada nuevo objeto sea instanciado desde el operador new vinculado al nombre identificador de la clase Padre. Además, constructor() es el que alimenta al nuevo objeto con las propiedades derivadas de la clase padre, siempre que un nuevo objeto 'nazca' mediante new.

Así mismo, no siempre una función constructor() de una clase deberá estar identificada con el término 'constructor'. Siempre que la función constructora este dentro de la clase y contenga las propiedades apuntadas con la clave this. podrá usar algún otro nombre identificador. Ver el ejemplo de abajo:

// Clase Padre
class SumaFriki {
// Función constructora PERO con nombre friki
  blablaConstructor(x) {
/* Propiedad de nombre 'x' se le asignará 
lo que se inserte como argumento 
al invocar nuevo objeto */
    this.x = x;
  }

// Nuevo método con operaciones
  restaFriki() {
    return 85 - this.x;
  }

// Nuevo método con operaciones
  divisionFriki() {
    return 125 / this.x;
  }
}

/* Variable de contexto para instanciar 
nuevo objeto */
var circle = new SumaFriki();

/* Se invoca CLASE, PERO se 
agrega un valor el argumento que da 
al parámetro que contiene la clase SumaFriki */
circle.blablaConstructor(5);

/* Se invoca uno de los métodos*/
console.log(circle.divisionFriki());
//-> 25

/* Se invoca uno de los métodos*/
console.log(circle.restaFriki());
//-> 80

Sin embargo, cuando en el siguiente código cambiamos el término 'constructor' por otro, el código resulta en 'undefined'. ¿Porqué entonces decimos que un constructor() puede tener otro identificador?

class Person {
/* Si cambiamos el nombre de 'constructor' 
con otro nombre, ese valor resulta en 'undefined' */
  Blabla(name) { this.name = name; }

// Método que apunta a propiedad 'name'
  saludo() { return `Soy ${this.name}`; }
}

var nombre = new Person("Juan");
console.log(nombre.saludo());
// Con constructor cambiado a Blabla
//-> Soy undefined

// Con constructor()
//-> Soy Juan

/* Se muestra undefined porque las propiedades 'name' 
no se detectan definidas en el objeto, peor en la clase. */

Ocurre que, cuando el constructor de una clase no es llamado de forma explícita; es decir, desde el identificador de la variable de instancia de un objeto, el código reconoce en la palabra 'constructor' como la única función agregadora de propiedades en el nuevo objeto.

En Javascript

class Person {
/* constructor() con otro nombre identificador 
y con parámetro */
  holaConstructor(name) 
{ this.name = name; }

// Método que apunta a la propiedad de la clase
saludo() { return `Soy ${this.name}`; }
}

// Variable de instancia de nuevo objeto
// Se crea objeto con operador new y se invoca a la Clase Person
var nombre = new Person;

/* Se invoca de forma explícita al nombre 
del método constructor() y se agrega un valor 
en el argumento */
nombre.holaConstructor("Pedro")

console.log(nombre.saludo());
//-> Soy Pedro

Finalmente, hacer nuestra propia invocación directa al nombre del identificador del constructor se conoce como invocación explícita. Abajo dos ejemplos:

// Clase
class Perrito {
// Método
  ladrido() {
    console.log('Guau!');
  }
}
// Crear variable de instancia
const firulais = new Perrito();

// Llamado explícito de instancia al método Ladrido()
firulais.ladrido(); 
//-> Guau!

// -+-+-+-

// -- OTRO EJEMPLO --
class Person {
// Método con argumento
  saludo(nombre) {
    console.log(`Holi, ${nombre}!`);
  }
}
// Instancia de clase desde variable
const nuevaPerson = new Person();

// Explicitly call the saludo method on the person instance
nuevaPerson.saludo('Sandra'); 
//-> Holi, Sandra!

COMPOSICIÓN o 'Aggregation'

Significa que en lenguajes orientados a objetos es posible seccionar el código en parcelas más pequeñas. Esas parcelas pueden ser objetos contenidos en otros objetos. En el ejemplo de abajo, el objeto vehiculo contiene como argumento un objeto recién instanciado: el objeto 'placa':

En Javascript

// Clase 
class Maquina {
// constructor con parámetro  
  constructor(marca) {

/* Apunta a valor de parámetro 
para luego alimentar a objeto hijo */
    this.marca = marca;
  }
}

// Nueva clase 
class Auto {
// constructor con parámetro  
  constructor(placa) {
/* Apunta a valor de parámetro 
para luego alimentar a objeto hijo */
    this.placa = placa;
  }
}

/* Crear nuevo contexto de instancia y 
nuevo objeto con argumento 'GYE-343' */
const placa = new Maquina("GYE-343");

/* Crear nuevo contexto de instancia y 
nuevo objeto con LA VARIABLE DE CONTEXTO DE INSTANCIA 
CREADA ARRIBA */
/* El objeto 'vehiculo' contiene el objeto 'placa' */
const vehiculo = new Auto(placa);

console.log("La placa es " + vehiculo.placa.marca);
//-> La placa es GYE-343

Un ejemplo final de agregación. Pero antes, tomar en cuenta lo siguiente: en las clases Estudiante y Cursos existe una propiedad 'nombre'. Esta propiedad es única para cada clase aunque se muestre con el mismo nombre. Ya que cada clase representa un contexto diferente: el nombre del curso es un valor diferente al nombre del profesor.

En Javascript

// Clase
class Curso {
  constructor(nombre, profe) {
// Propiedad 'nombre' única de la clase 'Curso'
    this.nombre = nombre;
// Otra propiedad
    this.profe = profe;
  }
}
// Clase
class Estudiante {
// Propiedad 'nombre' única de la clase 'Estudiante'
  constructor(nombre, grado) {
    this.nombre = nombre;
// Otra propiedad
    this.grado = grado;
  }
}

// Variables de instanciación
// Variable math guarda instancia de Curso
const math = new Curso("Math", "Dudu");
// Variable ciencia guarda otra instancia de Curso
const ciencia = new Curso("Science", "Sandra");
// Variable curso guarda objetos math y ciencia
// Este es un dato arreglo
const curso = [math, ciencia];
// variable estudiante guarda instancia de Estudiante
const estudiante = new Estudiante("Juanito", curso);

console.log(math);
// Curso { nombre: 'Math', profe: 'Dudu' }

console.log(ciencia);
// Curso { nombre: 'Science', profe: 'Sandra' }

console.log(curso);
/* Muestra en forma de arreglo los dos 
objetos con sus propiedades y valores */
/* [
  Curso { nombre: 'Math', profe: 'Dudu' },
  Curso { nombre: 'Science', profe: 'Sandra' }
] */

console.log(estudiante);
/* Estudiante {
  nombre: 'Juanito',
  grado: [
    Curso { nombre: 'Math', profe: 'Dudu' },
    Curso { nombre: 'Science', profe: 'Sandra' }
  ]
} */

HERENCIA

Para Antani y Stefanov[4] heredar clases significa "reusar código existente" en donde las propiedades de una clase madre serán heredadas hacia nuevos objetos instanciados.

En este contexto, los términos súper clase o clase padre refiere a lo mismo; es decir, a la gran clase desde la cual otras subclases heredarán sus propiedades y métodos.

En JS "los objetos se heredan entre objetos"[4], pero a su vez pueden actualizar los valores de propiedades según el contexto. Tres palabras claves son necesarias para generar herencia: extends, super y this.

    CLAVE EXTENDS
  • Usada dentro de la declaración de una subclase para crear una clase Hijo –o subclase– a partir de una clase Padre –o súper clase–.
  • Una vez creada la subclase, extends también abrió una via libre para acceder –cuando sea necesario– a los nombres y valores de las propiedades y los métodos reunidos en la clase Padre. Es decir, extends activa la vía para la herencia.
    CLAVE SUPER
  • Invoca y pide al constructor de la clase Padre el acceso total a las propiedades y métodos del Padre para luego usarlas en cada nueva instancia de clase.
  • No se puede invocar propiedades antes de usar super. Es decir, primero se usa super(); y luego, se apunta a la propiedad deseada. Ejemplo: super(.., ..) this.propiedad = propiedad;

Las diferencias entre las claves extends y super versus la función constructor() son:

constructor() extends super()
Método que da define las propiedades y valores iniciales desde la clase Padre para que, luego de ser creado un nuevo objeto, este herede esa estructura. Crea una clase hijo a partir de la clase Padre. Conecta con la función constructor de la clase Padre y lleva las propiedades y métodos al hijo.

Entonces, el constructor() define desde el Padre la futura estructura de propiedades y valores de un objeto a crear. Mientras que extends crea clase hijo y super conecta hijo con Padre. Por ejemplo:

En Javascript

// Clase
class Animal {
// Constructor con parámetros de propiedades
  constructor(nombre, tipoDeAnimal) {
// Se apunta a propiedades
    this.nombre = nombre;
    this.tipoDeAnimal = tipoDeAnimal;
  }
  
// Método de clase 
  ladridoSuave() {
// Muestra
    console.log("guu guu!");
  }
}

/* Ahora Se crea subClase a partir de SuperClase Animal */ 
class Perro extends Animal {

/* Tomará los valores de la propiedad 'nombre', 
a la vez que crea otra propiedad de nombre 'raza'... */
  constructor(nombre, raza) {

/* La clave super llama al constructor 
de la clase Animal para que la nueva clase 'Perro' 
obtenga las propiedades de 'nombre' y tipoDeAnimal' */
    super(nombre, "Perro");
    this.raza = raza;
  }
  
// Método de subclase
  ladridoFuerte() {
    console.log("WOOF! WOOF!");
  }
}

// Contexto de instancia con variable
/* Invoca la subClase Perro y alimenta 
las propiedades de nombre y raza */
let Nicoletto = new Perro("Nico", "Runa");

// Se invoca variable de instancia y propiedad 'nombre'
console.log(Nicoletto.nombre);  
// Nico

// Se invoca variable de instancia y propiedad 'tipoDeAnimal'
console.log(Nicoletto.tipoDeAnimal);  
// Perro

// Se invoca variable de instancia y propiedad 'raza'
console.log(Nicoletto.raza);  
// Runa

// Se invoca método de Clase
Nicoletto.ladridoSuave();  
// guau!

// Se invoca método de subClase
Nicoletto.ladridoFuerte();  
// WOOF! WOOF!

En otro ejemplo, la clase Vehiculo con las propiedades 'nombreCarro' y 'modelo' tendrá en cada nuevo objeto esas mismas propiedades.

// Clase madre
class Vehiculo {
    // Constructor con argumento de la variable
    constructor(marca) {
    /* Apuntamos a variable para que se 
    reinicialice con un argumento */
    this.nombreCarro = marca;
    }

    presente() {
    /* Muestra el valor apuntado al 
    contexto del constructor */ 
    return '** ' + this.nombreCarro;
    }
} // Cierra class Vehiculo

/* extends pide que Modelo sea
una clase hijo de Vehiculo */
class Modelo extends Vehiculo {
  constructor(marca, mod) {
  super(marca);
  this.Modelo = mod;
  }

  show() {
  return this.presente() + 
' modelo ' + this.Modelo;
  }
}
// NUEVOS OBJETOS 
let carroComprado1 = 
new Modelo("Jeep", "Compass");

let carroComprado2 = 
new Modelo("Renault", "Silver");

console.log(carroComprado1.show());
console.log(carroComprado2.show());

//- > ** Jeep modelo Compass
//-> ** Renault modelo Silver

s

En un ejemplo final, note como la clave super() es usada tanto para conectar con las propiedades de una clase padre como para conectar con los métodos también de la clase Padre y así, traerlos hacia la clase hijo. Este ejemplo fue tomado de Anurag Majumdar en Medium.

En Javascript

// Super clase o clase Padre
class Animal {
/* Función constructora con dos parámetros 
en el papel de propiedades */
constructor(nombre, peso) {
// Se apuntan parámetros y se los inicializa
this.nombre = nombre;
this.peso = peso;
}

// Método de clase
come() {
// Apunta a propiedad de clase
return `${this.nombre} ...come carne.`;
}

// Método de clase
duerme() {
// Apunta a propiedad de clase
return `${this.nombre} ...duerme.`;
}
// Método de clase
despierta() {
// Apunta a propiedad de clase
return `${this.nombre} ...esta despierto.`;
}
}

/* Se crea una subClase o clase hijo desde superClase Animal */
class Gorila extends Animal {
// constructor() obtiene las propiedades de la clase Padre 
constructor(nombre, peso) {
// Obtiene los valores de la clase Padre
super(nombre, peso);
}
// Método de subclase
trepa() {
// Se apunta a propiedad nombre
return `${this.nombre} ...sube árboles.`;
}

// Método de subclase
golpeaPecho() {
// Se apunta a propiedad nombre
return `${this.nombre} ...golpea pechito.`;
}

/* OJO aquí la CLAVE SUPER es usada como objeto. 
Llama a los métodos de la clase Animal de 
forma explícita */
// Método de subclase
seMuestraSano() {
// Se apunta a propiedad nombre
return `${super.come()} 
${this.golpeaPecho()}`;
}
// Método de subclase
suRutinaDiaria() {
// Se apunta a propiedad nombre
return `${super.despierta()} 
${this.golpeaPecho()} 
${super.come()} 
${super.duerme()}`;
}
}

// Método global -no está en ninguna clase-
function mostrar(mostrarElArgumentoQseAgregue) {
console.log(mostrarElArgumentoQseAgregue);
}

// Se crea VARIABLE de instancia de nuevo OBJETO
/* Se alimenta con las propiedades de la clase 
Padre pero con nuevos valores */
const gorilla = new Gorila('George', '160Kg');

/* Se usa la función 'mostrar' para insertarle 
la invocación del objeto + los métodos 
de la subclase 'gorilla' */
mostrar("* " + gorilla.golpeaPecho() + "\n" );
// George ...golpea pechito.

mostrar( "* " + gorilla.duerme() + "\n" );
// George ...duerme.

mostrar( "* " + gorilla.seMuestraSano() + "\n" );
// George ...come carne. George ...golpea pechito.

mostrar( "* " + gorilla.suRutinaDiaria() + "\n" );
/*->
George ...esta despierto. 
George ...golpea pechito. 
George ...come carne. 
George ...duerme.
*/

Herencia > Clave extends

La clave extends señala que una clase deriva o es hija de una clase madre. En el ejemplo inferior, la clase 'Madre' tiene como hijo a la clase 'hijo'; y así mismo, 'hijo' nace de la clase 'Madre'.

Herencia > Clave super

La clave super llama al constructor desde la clase madre y dispone que propiedades y métodos se hereden de inmediato.

Finalmente, en el código de abajo se muestran tres acciones: primero, establecer la clase 'Madre' con dos propiedads iniciales –edad y sexo–. Segundo, establecer que 'hijo' deriva de la clase 'Madre', con una nueva propiedad 'colorCabello'. Y tercero, crear una variable que almacene cada nueva instancia con las propiedades heredadas y con nuevos valores.

 // Clase con propiedades asignadas
class Madre {
      constructor(edad, sexo){
      this.edad = edad;
      this.sexo = sexo;
      }
}
// Nace una clase hijo
class hijo extends Madre{
// Se heredan propiedades
constructor(edad, sexo, colorCabello)
      {
      super(edad, sexo);
// Se asigna propiedad
      this.colorCabello = colorCabello;
      }
}

/* Se crea variable que guarda 
nueva instancia que hereda propiedades. 
Pero aquí se agregan valores a esas propiedades */
let dudu = new hijo(35, "hombre", "negro");
console.table(dudu);
//-> Tiene 35 es hombre y cabello negro
/* 
┌──────────────┬──────────┐
│   (index)    │  Values  │
├──────────────┼──────────┤
│     edad     │    35    │
│     sexo     │ 'hombre' │
│ colorCabello │ 'negro'  │
└──────────────┴──────────┘
*/

/* Nueva variable que guarda otra instancia */
let adriana = new hijo(38, "mujer", "rojo");
console.table(adriana);
/* 
┌──────────────┬─────────┐
│   (index)    │ Values  │
├──────────────┼─────────┤
│     edad     │   38    │
│     sexo     │ 'mujer' │
│ colorCabello │ 'rojo'  │
└──────────────┴─────────┘
*/

POLIMORFISMO

Polimorfismo o 'muchas formas' se define como "llamar al mismo método o acción desde diferentes objetos"[4] en donde cada método tendrá un comportamiento diferente –o muchas formas de realizar la ejecución– desde cada objeto. Un ejemplo sencillo son los datos numéricos. Una vez operados estos datos cambian -restados, sumados, multiplicados, etc–.

En el siguiente ejemplo, una sola función –operar(x,y) y operar(a,b)– pero con diferentes parámetros ejecuta acciones diferentes según métodos de función:

// ABRE CLASE
class Padre {
  // función operar() y dos argumentos
  operar(x, y) { 
  console.log(x * y);
  }
}

/* Activar herencia: Hijo hereda 
propiedades de Padre */
class Hijo extends Padre {
  // Otra ves la función operar() 
  operar(a, b) {
  // super : heredar propiedades y métodos
  super.operar(a, b);
  console.log('Holi!')
  }
}

var objeto1 = new Hijo();
var mostrar = objeto1.operar(2, 3);

//-> 6
//-> Holi!

Finalmente, un interesante artículo amplia ejemplos de ese concepto. Entre ellos:

  • Múltitrabajo de los operadores: por ejemplo, el signo '+' sirve para sumar pero también para concatenar datos alfanuméricos. Es decir, su comportamiento cambia según el contexto a ser usado.

  • Múltitrabajo en funciones: los datos de una misma función puedan dar diferentes resultados según nuevos datos insertados.

  • Coerción de tipo: Un valor puede ser convertido en otro según condiciones. Por ejemplo, un valor booleano True a False.

  • Múltitrabajo en variables: Tipos de variables pueden contener diferentes tipos de datos: let dia="lunes", semana= 8;

  • Polimorfismo paramétrico: Datos pueden contener datos, como en el caso de los objetos y arreglos.

  • Polimorfismo de subtipo:Objetos padres derivan de ellos otros objetos hijos, también llamado herenciao subclase.

En cuanto a la herencia –derivar propiedades creadas desde una clase padre y dirigirlas cada nueva entidad– se debe usar la clave this para apuntar a una propiedad ubicada en un contexto diferente del que se lo invoca:

/* El ejemplo base se tomó de 
https://zellwk.com/blog/polymorphism-javascript/ */ 

// Clase padre
class Amiwis {
/* Se vincula un parámetro al que será 
el creador de nuevos objetos */
  constructor(nombre) {
/* Se le da a la variable 'nombre' un apuntador 
mediante 'this' para invocarlo su valor 
desde fuera de esta función */
    this.nombre = nombre;
  }
// Método o función
  saludar() {
/* ATENCIÓN! Cuando invoquemos el valor del argumento,
 debemos usar 'this' para apuntar al valor 
del parámetro del constructor. */
    console.log(`Qué tal, soy ${this.nombre}`);
  }
}

/* ¡¡ALERTA!! No hay paréntesis () luego del nombre de la
 clase */
class Compitas extends Amiwis {
  saludar() {
/* ATENCIÓN! Cuando invoquemos el valor del argumento,
 debemos usar 'this' para apuntar al valor 
del parámetro del constructor. */
    console.log(`Qué tal, soy  ${this.nombre}.`);
  }
}

class Panitas extends Amiwis {
  saludar() {
/* ATENCIÓN! Cuando invoquemos el valor del argumento,
 debemos usar 'this' para apuntar al valor 
del parámetro del constructor. */
    console.log(`Qué tal, soy  ${this.nombre}.`);
  }
}
// Usamos 'new' para crear objeto 
const nombre1 = new Amiwis("Bruno");
const nombre2 = new Compitas("Rick");
const nombre3 = new Panitas("Dudu");

// Derivamos de método 'saludar'
nombre1.saludar();  
nombre2.saludar(); 
nombre3.saludar(); 

/* -> 
Qué tal, soy Bruno
Qué tal, soy Rick.
Qué tal, soy Dudu.
*/



OBJETOS (II)

Los siguientes son objetos globales –accesibles desde fuera de funciones– ya insertos en JS para operaciones matemáticas. Cada objeto agrupa invisibles propiedades que alteran datos numéricos. alerta que algunos navegadores pueden dar resultados ligeramente diferentes en los cálculos: "This means that different browsers can give a different result".

Métodos del objeto Math

El objeto Math() contiene métodos internos que automatizan matemática compleja.

Math.pow()

Una de esos métodos es Math.pow(), cuyo primer argumento es un número –la base–; y el segundo argumento, las veces que se multiplica por sí mismo –el exponente–.

var a = Math.pow(2, 4);
console.log(a);
// --> 16

var b = Math.pow(2, 8/4);
console.log(b);
// --> 4

var c = Math.pow(2, -8);
console.log(c);
// --> 0.00390625

// Pide multiplicar el número 3 en 0.33 veces
console.log(Math.pow(3,1/3));
// R. 1.44224957...

Math.round()

Devuelve la conversión de un número decimal a un número entero, según lo siguientes criterios:

  • Si el decimal es mayor o igual a 0.5 --> la conversión resultará en el entero inmediato mayor: uno.
  • Si el decimal es menor a 0.5 --> la conversión será al entero inmediato menor: cero.
/* Decimal MENOR a 0.5...   */ 
console.log(Math.round(0.4));
// --> 0

console.log(Math.round(-0.2));
// --> -0

/* Decimal MAYOR o IGUAL a 0.5...   */ 
console.log(Math.round(0.6));
// --> 1

console.log(Math.round(3.1000));
// --> 3

console.log(Math.round(3.6000));
// --> 4

Operar desde una variable puede ser útil:

var calc = 55.332/11.4344;
console.log(calc);
console.log( Math.round(calc) );
// 4.8 –-decimal mayor a 0,5 --> redondea a 5

Math.ceil()

Devuelve la conversión decimal desde 0.1 a 0.9 hacia el entero inmediato más alto. A diferencia de Math.round(), que redondea hacia el entero más alto desde 0.5 a 0.9; Math.ceil() redondeará desde un decimal más bajo (0.1).

// Decimal
var num = 0.75;
// Redondea a entero inmediato
console.log(Math.ceil(num))
// --> 1

// Decimal
var num = 35.1;
// Redondea a entero inmediato
console.log(Math.ceil(num))
// --> 36

// Decimal
var num = 2.00000001;
// Redondea a entero inmediato
console.log(Math.ceil(num))
// --> 3

/* Incrementa el entero al mínimo decimal */
var num = -2.00000001;
// Redondea a entero inmediato
console.log(Math.ceil(num))
// --> -2

/* Negativos en cero redondean 
desde el mínimo decimal a 0 cero */
console.log(Math.ceil(-0.10));
// --> 0

Math.floor()

Redondea al entero inferior o igual del número dado más bajo aunque sea un decimal de 0.9

console.log(Math.floor(2.9999) );
// R. 2

console.log(Math.floor(10.9) );
// R. 10

console.log(Math.floor(0.9) );
// R. 0

Math.abs()

Devuelve el valor absoluto (sin signo)

console.log( Math.abs(-0.6));
// --> 0.6
console.log( Math.abs(-1.999999));
// --> 1.999999

Math.min() y Math.max()

Devuelve el entero o el decimal más grande -Math.max()- o el más bajo -Math.min()-. Permite números strings.

// Devuelve el número mayor
console.log(Math.max(9.6, "10",4));
/// R. 10 
/* Devuelve '10' aún siendo string. 
JS convierte internamente los strings 
literales en numéricos. */

// Devuelve el entero o decimal mayor
console.log(Math.max(0.6, "0.1",0.009999));
// R. 0.6

// Devuelve un entero o decimal más bajo
console.log(Math.min(1, 0.5, 2));
// R. 0.5

Math.sqrt()

Devuelve raíz cuadrada del argumento numérico dado.

console.log(Math.sqrt(-0)); // -0
console.log(Math.sqrt(2)); // 1.414213562373095
console.log(Math.sqrt(9)); // 3

console.log( Math.sqrt(2*3/100) );
// 0.2449489742783178

Math.cbrt()

Da la raíz cúbica de argumento numérico.

 // Raíz cúbica de (argumento)
// ¿Qué número, multiplicado 
// tres veces, da (el argumento)?
console.log(Math.cbrt(8));
// --> 2

Math.hypot()

Retorna la raíz cuadrada de la suma de dos valores –los dos lados de un triángulo rectángulo–, por ejemplo.

var ladoA = 3, ladoB = 6;
console.log(Math.hypot(ladoA,ladoB));
// 6.708203932499369

/* Para calcular la distancia entre dos dimensiones */
console.log(Math.hypot(3, 4, 3));
// 5.830951894845301

/* Para calcular la distancia entre dos puntos cartesianos */
// A=(x,y) B=(x,y)
console.log(Math.hypot( (1,2), (4,6) ) );
// 6.324555320336759

Math.trunc()

Expresa un entero decimal quitando el decimal.

console.log(Math.trunc(3.2) );
// 3 

console.log(Math.trunc(-2.444) );
// 2
/*  Se mantiene negativo. 
Si dese desea quitar el negativo, 
entonces va Math.abs() */

var num = 3.4*3.98/9.908;
console.log(Math.trunc(num));
// 1

// Convertir a entero quitando el decimal
console.log(Math.trunc(-0.1122) );
// 0

Math.random()

Devuelve un número aleatorio decimal, entre 0 y 1.

Un ejercicio clásico es mostrar un número aleatorio según límites dados por un rango:

var aleat = Math.random();
// 0.283... o cualquiera

// Rango: Dame números desde 0 hasta el 100
var centenar = 100;

// Opción: Rango hasta el 1000
var miles = 1000;

// Multiplica el resultado random por el límite
var opera = centenar * aleat;

// Resultado con decimales. 
// Necesitamos redondear
console.log((opera));
// --> 34.85451320941382

// NÚMERO FINAL
// Redondea el resultado al entero más bajo
console.log( Math.floor(opera) );

Una estructura más simplificada para el mismo resultado sería el siguiente –donde el número sería el rango límite–.

/* Desde dentro hacia afuera, 
Math.random() da un número decimal entre 0 y 1. 
Luego, multiplica ese número por 10. 
Como sigue siendo decimal, Math.floor() 
lo convierte al entero inferior inmediado. */
console.log(Math.floor( Math.random() * 10) );
/* En cada actualización, 
se mostrará un nuevo valor */
/* 5...2...0....1 (...) */

TRABAJAR CON ARGUMENTOS Y PARÁMETROS

Para la Guía de Visual Basic de Microsoft existen diferencias entre parámetros y argumentos.

  • Parámetros: es el identificador –nombre– de un valor, que a su vez es contenido en sí mismo como una variable que se define en un contexto, como una función. Este parámetro podrá recibir un valor llamado argumento.
  • Argumento: es un valor/objeto –numérico, alfanumérico, literal– ingresado en el parámetro. Ese valor será llamando cuando el contexto –una función, por ejemplo- sea invocada. Los argumentos estan rodeados por paréntesis ().

Para la PC Magazine enciclopedia "los argumentos son valores independientes" que proveen de más datos a un contexto, como una función. Finalmente, es el usuario final quién decidirá qué valor podría ese argumento. La expresión 'pasar un argumento' significa que se ha alimentado con un valor a un parámetro.

¿Qué puede ser un argumento?

  • Un valor numérico, un string, booleano.
  • Una función
  • Una palabra clave, como this
  • Un método

En otras palabras, un parámetro es el identificador –o nombre– de una variable; y un argumento es el valor enviado al parámetro.

Similar a una bodega con dos grandes puertas que permiten ingresar objetos, las puertas de ingreso son los parámetros –con un letrero identificatorio que son los nombres de cada puerta–; mientras que los argumentos son los objetos ingresados externamente.

 // Se crea la función
function proceso(parametro1, parametro2) {

// Se pide devolver -cuando se requiera- los parámetros
return "Entraron " + parametro1 + " y " + parametro2;
}

// Se alimenta DE ARGUMENTOS los parámetros
console.log(proceso("objeto1", "objeto2"));
// Entraron objeto1 y objeto2

// * * * * * * * * 
// * * * * * * * * 
// * * * * * * * * 

// Se crea la función
function bodega(puerta1, puerta2) {

// Se pide devolver -cuando se requiera- los parámetros
return "Entraron " + puerta1 + " y " +puerta2;
}

// Se alimenta DE ARGUMENTOS los parámetros
console.log(bodega("silla", "escritorio"));
// Entraron silla y escritorio

FUNCIONES PREDEFINIDAS

Ya planteamos que las funciones son territorios delimitados cuyo papel es trabajar con datos desde un solo bloque de código; y nos permiten –mediante parámetros– insertar nueva información –en forma de argumentos–.

// parámetro 'lugar'
function turismo(lugar){

// Condicional + ubicación dada
if (lugar == "cerro Santa Ana"){
console.log("El " + lugar + 
" se ubica en el centro de Guayaquil");

} else if (lugar == "Malecón 2000"){
console.log("El " + lugar + 
" se ubica frente al río Guayas");

} else {
console.log(lugar + " No está en Guayaquil");
}}

// INVOCAR FUNCIÓN
// Argumento 'Malecón 2000'
turismo("Malecón 2000");
/* --> El cerro Santa Ana 
se ubica en el centro de Guayaquil */

Como veremos, otras funciones ya predefinidas nos serán muy útiles.

parseInt()

Solo si un dato numérico esta ubicado antes que uno alfanumérico, entonces parseInt() devuelve solo el dato numérico entero. Si parseInt() no puede convertir los primeros números a enteros, retorna la propiedad NaN. Además de eso, parseInt() convierte un valor radix. Este determina el sistema numérico. Por ahora, solo mantendremos la conversión de alfanumérico a dato tipo número.

// parseInt()
var ingreso = '2023Guayaquil';
// Solo muestra el primer conjunto de números
console.log(parseInt(ingreso));
// --> 2023

// ...y sigue siendo string
console.log(typeof ingreso);
//-> string

// No puede convertir si el entero está al final
console.log(parseInt("Edu2023"));
// NaN

/* Devuelve NaN si el dato alfanum. está al final. 
El sistema lo detecta una inválida 
representación de un número  */
var ingreso = 'Guayaquil 2023';
console.log(parseInt(ingreso));
// NaN

/* Devuelve solo el entero  */
var ingreso = '20.98453 Holi';
console.log(parseInt(ingreso));
// 20

parseFloat()

Busca el primer conjunto de decimales y los muestra, obviando otros caracteres que no sean números.

// parseFloat()
var ingreso = '0.1416...es PI';
console.log(parseFloat(ingreso));
// 0.1416

// --> Sigue siendo string
console.log(typeof ingreso);
// string

// También convierte exponentes
console.log(parseFloat('123e-2'));
// --> 1.23

// El entero lo muestra igual
console.log(parseFloat('23234'));
// 23234

// No convierte
console.log(parseFloat("a1234"));
// --> NaN

isNaN()

Not a Number. Muestra true cuando su argumento es un dato no numérico. Muestra false cuando su argumento sí es un dato numérico. Es decir, si NaN() contiene un dato numérico, negarlo mostrará false.

console.log(NaN(10)); console.log(NaN("holi"));
Afirma que el número 10 No es un Número. La expresión da false. Afirma que string "holi" no es un Número. La expresión da true.

 // El dato NO es número
console.log(isNaN("abc"));
// --> true

// lo indefinible no es número
console.log(isNaN(undefined));
// --> true

// Lo vacío es no es un no número
console.log(isNaN(null));
// --> false

// Negar que 200 es un número es erróneo. 
console.log(isNaN(200));
// --> false

// Negar la negación da una afirmación:
// 200 sí es un número 
console.log(!isNaN(200));
// --> true

eval()

Cuidado. Esta función es un hacha afilada. Permite insertar líneas de texto como si fueran códigos ejecutables. Bien usada es aplicada en pruebas. Pero en manos equivocadas, es un peligro. SE RECOMIENDA EVITAR SU USO. Para más referencias, revisar aquí y aquí. Finalmente, en el artículo Amit Khonde detalla los problemas de desempeño y de seguridad que tiene esa función.

/* Este podría ser un código malicioso */
eval(' var ojito = "Cuidado"; ');
console.log(ojito);
// --> Cuidado

// También este
let expression = "2 + 2";
let result = eval(expression);
console.log(result); 
// 4

Un ejemplo válido de uso con eval() y plantillas literales sería el siguiente:

En Javascript

/* OJO: los {{ }} no deben mostrarse separados */
/* La variable en {{ }} debe mostrarse centrada {{ variable }} */
/* Las comillas de "{{ }}"  no deben separarse */
/* Usar comillas simples dentro de console... */
/* La expresión de console.log() no se puede cortar. */
let linea = "console.log('Es' + ' ' + ({{ expression }}) + '.')";
let expression = "2 + 2";
let code = linea.replace("{{ expression }}", expression);
eval(code); 
// La respuesta es 4.

Alternativas para eval() son el método JSON.parse() o plantillas literales.


Subir al inicio Avanzar