Skip to content

02. Estructuras de Datos

Carlos Jaramillo edited this page Dec 27, 2021 · 31 revisions

Objetos and (JSON)

Se declara con { clave-LlaveKey: 'valorValue' } la clave puede ser un número o un string y el valor puede ser una función, número, decimal, booleano, string, etc..

  • Los Objetos que se pasen por parametro a una función o asignen a una nueva variable, se pasan por referencia (si los modificamos adentro de la función, se va ver afectado el objeto afuera también) - Side Effect
  • __proto__ or Object.getPrototypeOf(objectWhoIWant) nos retorna el prototipo con el que fue creado este objeto.
    • todos los objetos creados con notacion JSON, tienen como prototipo "object"
var carlos = {
    nombre: 'Carlos',
    apellido: 'Jaramillo',
    edad: 22,
    direccion: {
       casa: 'cra66b #30b 40'
    }
} 
const personas = {
    // carlos: carlos
    carlos // is equals if both have the same name
}
    
function imprimirNombre(persona) {
    console.log(persona.nombre.toUpperCase());
}
imprimirNombre(carlos);



let user = { nombre: "Carlos" };
typeof user; // "object"

// No es una forma fiable de traer el prototype
// Porque varía según el entorno de ejecución o browser
user.__proto__
// is better
const prototypeOfUser = Object.getPrototypeOf(user)



let animal = Object.create(null); // empty object
animal.vivo = true
animal.estaVivo = function() { this.Vivo && console.log('Sigue vivo') }

// HERENCIA DE PROTOTIPOS
let perro = Object.create(animal); // object as a prototype hyerachi works with DELEGATION

Getters y Setters

Son funciones que podemos utilizar dentro de objetos que nos permiten tener propiedades virtuales, no es una propiedad que existe directamente en el objeto.

let persona = {
  nombre: 'Eduardo',
  apellido: 'Franco',
  get nombreCompleto() {
    return`${nombre} ${apellido}`;
  },
  set nombreCompleto(name) {
    const palabras = name.split(' ');
    this.nombre = palabras[0] || '';
    this.apellido = palabras[1] || '';
  }
}
persona.nombreCompleto = 'Carlos Jaramillo';

Proxy

Sirve para interceptar la lectura de propiedades de un objeto (los get, y set) entre muchas otras funciones. Así, antes de que la llamada llegue al objeto podemos manipularla con una lógica que nosotros definamos.

// objeto a supervisar (sus props pueden ser objetos, array, funciones, u otro proxy)
const target = {
  red: 'Rojo',
  green: 'Verde',
  blue: 'Azul'
}
// handler es un objeto con funciones (trampa)
// definen las acciones a seguir cuando se accede al objeto supervisado
const handler = {
  get(obj, prop) {
    if (prop in obj) {
      // si la propiedad existe, pues retornamos su valor
      return obj[prop]
    }

    // validar si podemos retornar una sugerencia
    const suggetion = Object.keys(obj).find(key => {
      // objeto con claves del objeto supervisado
      // retorno aquella (nombre) que su distancia sea <= 3
      // Levenshtein (librería que mide diferencia entre 2 palabras)
      return Levenshtein.get(key, prop) <= 3 
    })
    
    if (suggetion) {
      console.log(`${prop} no se encontró. ¿Quisiste decir ${suggetion}?`);
    }

    return obj[prop];
  }
}

const p = new Proxy(target, handler);

p.red; // "Rojo"
p.green; // "Verder"
p.reed //reee no se encontró. ¿Quisiste decir red?
p.geen //geen no se encontró. ¿Quisiste decir green?

Destructuring

Solo se usa cuando un atributo siempre se envía

// Asignación Desestructurante objetos
function imprimirNombre(persona) {
    // var nombre = persona.nombre;
    var { nombre } = persona; // Desestructurar Objetos // create new var called nombre
    var { apellido: lastName } = persona; // Desestructurar Objetos // create new var called lastName
    console.log(nombre.toUpperCase());
    console.log(lastName.toUpperCase());
    console.log(apellido); // 'apellido' is not defined (was called lastName)
}

imprimirNombre(carlos);

También podemos desestructurar en los props de una función y asignarle un valor por defecto:

function imprimirNombre({ nombre }) { 
    console.log(nombre.toUpperCase());
}

function imprimirNombreDefaultValue({ nombre = 'None' }) { 
    console.log(nombre.toUpperCase());
}
 
imprimirNombre(carlos);
imprimirNombre({ nombre: 'Pepito' }); // overwrite the current value

const {nombre, direccion} = carlos;
console.log(direccion); // will return a object with direccion

const {nombre, direccion: {casa}} = carlos;
console.log(direccion); // not defined
console.log(nombre,casa);

// Asignación Desestructurante Arrays
const personajes = ['Carlos', 'Luis', 'Pepe']

// El nombre de la variable no importa, si no el orden
const [p1] = personajes; // will return Carlos
// When you want skip a value, you can use comma (,)
const [, , p3] = personajes; // will return Pepe
personajes[2];

Spread Operator

Clona un objeto o array, usualmente usado para evitar el side effect. Si el objeto tiene otros objetos anidados, no los clona, los asigna por referencia

function nombreMayusculas(persona) {
    var { nombre } = persona; // Desestructurar Objetos
    return {...persona, nombre: nombre.toUpperCase()}
}

Arrays o Arreglos

Estructura de datos, que nos permite agrupar dato, pueden ser números, strings, booleanos, objetos, etc. para realizar ciertas operaciónes sobre esa colección. Funcionan igual que un objeto, ya que son OBJETOS. La única diferencia son los métodos propios de los arrays. Principales métodos

// se declara con corchetes
var personas = [];
var personas2 = new Array(); // not commonly used, only when you will define initial length
var personas3 = new Array(10); // will create empty array with 10 positions

var person = [];
person.name = "Carlos"; // person = [ name: 'Carlos' ] 

var numeros = [1, 2, 3, 4];
var vocales = ['a', 'e', 'i', 'o', 'u'];

// para acceder a una posición
vocales[1];

personas[0].altura;
personas[0]['altura'];

// Special behavior arrays just numerical indices
y = []; // []
y[0] = true; // [true]
y.length; // 1
y.hello = 'goodbye'; // [true, hello: 'goodbye']
y.length; // 1



// but you can add more with persona3.push(1)
// to add one or more elements at the final of array
// personas3.push(2); // not is needed, capture the return
const personas3Length = personas3.push(2, 3); // añade un 2 y un 3 // return the array.length

// to add one or more elements at the start of a array
const personas3Length = personas3.unshift(2, 3); // añade un 2 y un 3 // return the array.length // not is needed, capture the return

// no es tan común usar push, o unshift, porque modifica el objeto inicial (side effect), usualmente se usa spread operator
let personas4 = [...persona3, 2] // lo añade sin mutar el primer array

const deletedF = persona3.shift(); // delete the first element of the array, and return it. // not is needed, capture the return
const deletedL = persona3.pop(); // delete the last element of the array, and return it. // not is needed, capture the return

// loop array without mutate that, use forEach

// Create new array with all sub-array elements concatenated into it recursively up to the specified (default 1) depth.
let arr1 = [1, 2, [3, 4]];
arr1.flat();  // [1, 2, 3, 4]

let arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2); // [1, 2, 3, 4, 5, 6]
arr3.flat(Infinity); // [1, 2, 3, 4, 5, 6]

// tests whether at least one element in the array passes the test implemented by the provided function.
const even = (element) => element % 2 === 0;
console.log(array.some(even)); //  It returns a Boolean value.

// returns a shallow copy of a portion of an array into a new array object selected from start to end (end not included) 
// arr.slice([inicio [, fin]])
console.log(vocales.slice(2, 4)); // will return, positions 2 and 3

// changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
const months = ['Jan', 'March', 'April', 'June'];

// months.splice(_start_, _deleteCount_, _optional_elementsToAdd_);

array.splice(index, 1); // Remove specific index from array

months.splice(1, 0, 'Feb'); // inserts at index 1
console.log(months); // expected output: Array ["Jan", "Feb", "March", "April", "June"]

months.splice(4, 1, 'May'); // replaces 1 element at index 4
console.log(months); // expected output: Array ["Jan", "Feb", "March", "April", "May"]

Filtrar elementos en un array (filter)

const numeroPar = numero => numero % 2 === 0;

// la condición en filter debe ser una función
var numerosFiltrados = numeros.filter(numeroPar);
// retorna un nuevo array

// con función anonima
var numerosFiltrados = numeros.filter(function (numero) {
    return numero % 2 === 0;
});

Transformar un array (map)

Itera sobre los elementos de un array, en el orden de inserción y retorna un array nuevo con los elementos modificados.

const multiplicarPorDos = numero => numero * 2;
var numerosMultiplicados = numeros.map(multiplicarPorDos)

// también puede ser llamado con una función anonima
var numerosMultiplicados = numeros.map(function (numero) {
    return numero => numero * 2;
});

const pasarAlturaAcms = persona => {
    // personal.altura *= 100;
    // return persona;

    // retornar nuevo objeto, para no modificar la persona inicial
    return {
        ...persona,
        altura: persona.altura * 100
    }
}

// para retornar simplemente un objeto
const pasarAlturaAcms = persona => ({
    ...persona,
    altura: persona.altura * 100
})

Reducir un array a un valor

/*
var acum = 0
for (var i = 0; i < personas.length; i++) {
    acum = acum + personas[i].cantidadDeLibros
}
console.log(`En total todos tienen ${acum} libros`)
*/
// (una función, y el valor inicial del acumulador)

// acumulador y cada uno de los elementos elementos
const reducer = (acum, {cantidadDeLibros}) => acum + cantidadDeLibros;

var totalDeLibros = personas.reduce(reducer, 0)

NodeList

// Esto retorna un NodeList
const buttons = document.getElementsByClassName("call-to-action");

// no funciona porque no es una rray
buttons.forEach(button => {
  button.onclick = () => alert('Nunca pares de aprender');
});

//Podría funcionar si lo convertimos a array
const buttons = Array.from(document.querySelectorAll('.class'))
const buttons = [...document.querySelectorAll('.class')]

Cambiar valores de array a valor estatico

const array1 = [1, 2, 3, 4];

// fill with 0 from position 2 until position 4
console.log(array1.fill(0, 2, 4));
// expected output: [1, 2, 0, 0]

// fill with 5 from position 1
console.log(array1.fill(5, 1));
// expected output: [1, 5, 5, 5]

console.log(array1.fill(6));
// expected output: [6, 6, 6, 6]
const buttons = document.getElementsByClassName("call-to-action");

// Obtenemos la función forEach de Un arreglo y le cambiamos this. 
// para que hagá referencia a buttons y de esté modo recorrer el arreglo.
Array.prototype.forEach.call(buttons, button => {
  button.onclick = () => alert('Nunca pares de aprender');
})

Prototipos (clases)

Javascript no soporta la herencia, porque no existen las clases. Existen los prototipos que son objetos que le vamos agregando metodos, que reciben funciones.

function Persona(nombre, apellido, altura) {
    // this = Object.create(Persona.prototype); - new hace esto por debajo
    this.nombre = nombre;
    this.apellido = apellido;
    this.altura = altura;
    // return this; - está implicito.
}
// siempre en la parte superior, porque si se ejecutan antes de ser declaradas no existen.
// no pueden ser arrow functions, porque dentro de una arrow function this, this hace referencia a su padre.
Persona.prototype.saludar = function() {
    console.log(`Hola, Me llamo ${this.nombre}`);
}

var carlos = new Persona('Carlos', 'Jaramillo', 1.72)

Función Constructora

En JS las clases no existen, los objetos se crean a partir de otros objetos, e incluso pueden heredar entre ellos.

  • Todas las funciones tienen la propiead .prototype, que es igual a la propiedad __proto__ de los objetos creados a partir de esta función. -- Puedes modificar el prototype de una función incluso después de haber generado objetos.
// Constructor Function
function Curso(title){
  this.titulo = title;
  this.inscribir = function(user) {
    console.log(user, ' subscribed');
  }
}

let testCourse = new Curso('Test Course');
let altCourse = new Curso('Alt Course');

testCourse.inscribir('Carlos')
console.log(altCourse.titulo);
console.log(testCourse.titulo);



function User() {}
user = new User();

console.log(user.__proto__);
console.log(User.prototype);

user.__proto__ == User.prototype; // true

// añadido después de instanciar `user`
User.prototype.saludar = function() {
  console.log('Hola');
}
user.saludar(); // funciona

let carlos = Object.create(user); // use the user prototype to create carlos.

carlos.saludar(); // works porque todos los objetos creados a partir de dicha funcion o heredados obtienen el metodo


function Admin() {}
Admin.prototype = new User();

let carlosAdmin = new Admin();
carlosAdmin.saludar(); // works

Clases (ECMA 2015)

En JS las clases no existen, es un lenguage orientado a objetos basado en prototipos y no a clases. esto es sugar syntax.

  • Have extends from other classes, constructor functions, or building objects like Array, Date.
  • you can call methods from the parent with super.method();
  • Can implement encapsulation with accessor methods get/set
  • Method does not need an object to be executed static
// class declaration
class Course {

  // only can have 1 constructor
  constructor(title) { // auto called when you instance a new object, to assign initial values or config
    this.titulo = title;
  }

  inscribir(user) {
    console.log(user, ' subscribed');
  }

  // to call get method doesn't need use ()
  get name() {
    return this.titulo;
  }

  // Always get 1 argument, cant get 0 or +1.
  set name(title) {
    this.titulo = title;
  }

  static testing() {
  }
}

let testCourse = new Course('Test Course');
console.log(testCourse.titulo);
testCourse.inscribir('Carlos');

testCourse.name = 'Changed Test Course';
console.log(testCourse.name);


// class expresion
let Course = class {}
let User = class User {}
class Persona {
    constructor(nombre, apellido, altura) {
        this.nombre = nombre;
        this.apellido = apellido;
        this.altura = altura;
    }

    saludar(fn) {
        // var nombre = this.nombre;
        // var apellido = this.apellido;
        var {nombre, apellido} = this;
        console.log(`Hola, Me llamo ${nombre}`);
        if (fn) {
            fn(nombre, apellido, null)
        }
    }

    soyAlto() {
        return this.altura > 1.8;
    }
}

class Desarrollador extends Persona {
    constructor(nombre, apellido, altura) {
        // llamar constructor clase padre (necesario para usar this)
        super(nombre, apellido, altura);
        /*
        this.nombre = nombre;
        this.apellido = apellido;
        this.altura = altura;
        */
    }
    
    saludar(fn) {
        var {nombre, apellido} = this;
        console.log(`Hola, me llamo ${nombre} y soy dev!`);            
        if (fn) {
            fn(nombre, apellido, true)
        }
    }
}

Herencia prototipal

Funciona con delegación. Entonces busca un metodo en si, si no lo encuentra lo busca en el prototipo padre, y así hacia arriba, hasta llegar al prototipo base de todos los prototipos (obj) 'Cadena de prototipos'. Frena cuando se topa un objeto cuyo prototipo es null y no encuentra el metodo o propiedad, retorna undefined.

function Desarrollador(nombre, apellido) {
    this.nombre = nombre;
    this.apellido = apellido;
}

// Herencia antes de ECMA 2015
function heredaDe(prototipoHijo, prototipoPadre) {
    var fn = function () {}
    fn.prototype = prototipopadre.prototype;
    prototipoHijo.prototype = new fn;
    prototipoHijo.prototype.constructor = prototipoHijo;
}

Desarrollador.prototype.saludar = function () {
    console.log(`Hola, me llamo ${this.nombre} y soy dev.`)
}

La función hasOwnProperty sirve para verificar si una propiedad es parte del objeto o si viene heredada desde su prototype chain.

carlos.hasOwnProperty('name'); // true
carlos.hasOwnProperty('saludar'); // false
// No es una forma fiable de traer el prototype
// Porque varía según el entorno de ejecución o browser
carlos.__proto__ 

// retorna el prototype de Carlos
const prototypeOfCarlos = Object.getPrototypeOf(carlos)

prototypeOfCarlos === Persona.prototype // true - misma posición en memoria

Method vs Propety

  • .length is a property
  • .pop() is a method (because has () to be called) function