Chapter 4Estructuras de Datos: Objetos y Arrays

En dos ocasiones me han preguntado, ‘Dinos, Sr. Babbage, si pones montos equivocadas en la máquina, saldrán las respuestas correctas? [...] No soy capaz de comprender correctamente el tipo de confusión de ideas que podrían provocar tal pregunta.

Charles Babbage, Passages from the Life of a Philosopher (1864)
Picture of a weresquirrel

Los números, los booleanos y los strings son los átomos que constituyen las
estructuras de datos. Sin embargo, muchos tipos de información requieren más de un átomo. Los objetos nos permiten agrupar valores—incluidos otros objetos— para construir estructuras más complejas.

Los programas que hemos construido hasta ahora han estado limitados por el hecho de que estaban operando solo en tipos de datos simples. Este capítulo introducira estructuras de datos básicas. Al final de el, sabrás lo suficiente como para comenzar a escribir programas útiles.

El capítulo trabajara a través de un ejemplo de programación más o menos realista, presentando nuevos conceptos según se apliquen al problema en cuestión. El código de ejemplo a menudo se basara en funciones y vinculaciones que fueron introducidas anteriormente en el texto.

El Hombre Ardilla

De vez en cuando, generalmente entre las ocho y las diez de la noche, Jacques se encuentra a si mismo transformándose en un pequeño roedor peludo con una cola espesa.

Por un lado, Jacques está muy contento de no tener la licantropía clásica. Convertirse en una ardilla causa menos problemas que convertirse en un lobo. En lugar de tener que preocuparse por accidentalmente comerse al vecino (eso sería incómodo), le preocupa ser comido por el gato del vecino. Después de dos ocasiones en las que se despertó en una rama precariamente delgada de la copa de un roble, desnudo y desorientado, Jacques se ha dedicado a bloquear las puertas y ventanas de su habitación por la noche y pone algunas nueces en el piso para mantenerse ocupado.

Eso se ocupa de los problemas del gato y el árbol. Pero Jacques preferiría deshacerse de su condición por completo. Las ocurrencias irregulares de la transformación lo hacen sospechar que estas podrían ser provocadas por algo en especifico. Por un tiempo, creyó que solo sucedia en los días en los que el había estado cerca de árboles de roble. Pero evitar los robles no detuvo el problema.

Cambiando a un enfoque más científico, Jacques ha comenzado a mantener un registro diario de todo lo que hace en un día determinado y si su forma cambio. Con esta información el espera reducir las condiciones que desencadenan las transformaciones.

Lo primero que el necesita es una estructura de datos para almacenar esta información.

Conjuntos de datos

Para trabajar con una porción de datos digitales, primero debemos encontrar una manera de representarlo en la memoria de nuestra máquina. Digamos, por ejemplo, que queremos representar una colección de los números 2, 3, 5, 7 y 11.

Podríamos ponernos creativos con los strings—después de todo, los strings pueden tener cualquier longitud, por lo que podemos poner una gran cantidad de datos en ellos—y usar "2 3 5 7 11" como nuestra representación. Pero esto es incómodo. Tendrías que extraer los dígitos de alguna manera y convertirlos a números para acceder a ellos.

Afortunadamente, JavaScript proporciona un tipo de datos específicamente para almacenar secuencias de valores. Es llamado array y está escrito como una lista de valores entre corchetes, separados por comas.

let listaDeNumeros = [2, 3, 5, 7, 11];
console.log(listaDeNumeros[2]);
// → 5
console.log(listaDeNumeros[0]);
// → 2
console.log(listaDeNumeros[2 - 1]);
// → 3

La notación para llegar a los elementos dentro de un array también utiliza corchetes. Un par de corchetes inmediatamente después de una expresión, con otra expresión dentro de ellos, buscará al elemento en la expresión de la izquierda que corresponde al índice dado por la expresión entre corchetes.

El primer índice de un array es cero, no uno. Entonces el primer elemento es alcanzado con listaDeNumeros[0]. El conteo basado en cero tiene una larga tradición en el mundo de la tecnología, y en ciertas maneras tiene mucho sentido, pero toma algo de tiempo acostumbrarse. Piensa en el índice como la cantidad de elementos a saltar, contando desde el comienzo del array.

Propiedades

Hasta ahora hemos visto algunas expresiones sospechosas como miString.length (para obtener la longitud de un string) y Math.max (la función máxima) en capítulos anteriores. Estas son expresiones que acceden a la propiedad de algún valor. En el primer caso, accedemos a la propiedad length de el valor en miString. En el segundo, accedemos a la propiedad llamada max en el objeto Math (que es una colección de constantes y funciones relacionadas con las matemáticas).

Casi todos los valores de JavaScript tienen propiedades. Las excepciones son null y undefined. Si intentas acceder a una propiedad en alguno de estos no-valores, obtienes un error.

null.length;
// → TypeError: null has no properties

Las dos formas principales de acceder a las propiedades en JavaScript son con un punto y con corchetes. Tanto valor.x como valor[x] acceden una propiedad en valor—pero no necesariamente la misma propiedad. La diferencia está en cómo se interpreta x. Cuando se usa un punto, la palabra después del punto es el nombre literal de la propiedad. Cuando usas corchetes, la expresión entre corchetes es evaluada para obtener el nombre de la propiedad. Mientras valor.x obtiene la propiedad de valor llamada “x”, valor[x] intenta evaluar la expresión x y usa el resultado, convertido en un string, como el nombre de la propiedad.

Entonces, si sabes que la propiedad que te interesa se llama color, dices valor.color. Si quieres extraer la propiedad nombrado por el valor mantenido en la vinculación i, dices valor[i]. Los nombres de las propiedades son strings. Pueden ser cualquier string, pero la notación de puntos solo funciona con nombres que se vean como nombres de vinculaciones válidos. Entonces, si quieres acceder a una propiedad llamada 2 o Juan Perez, debes usar corchetes: valor[2] o valor["Juan Perez"].

Los elementos en un array son almacenados como propiedades del array, usando números como nombres de propiedad. Ya que no puedes usar la notación de puntos con números, y que generalmente quieres utilizar una vinculación que contenga el índice de cualquier manera, debes de usar la notación de corchetes para llegar a ellos.

La propiedad length de un array nos dice cuántos elementos este tiene. Este nombre de propiedad es un nombre de vinculación válido, y sabemos su nombre en avance, así que para encontrar la longitud de un array, normalmente escribes array.length ya que es más fácil de escribir que array["length"].

Métodos

Ambos objetos de string y array contienen, además de la propiedad length, una serie de propiedades que tienen valores de función.

let ouch = "Ouch";
console.log(typeof ouch.toUpperCase);
// → function
console.log(ouch.toUpperCase());
// → OUCH

Cada string tiene una propiedad toUpperCase (“a mayúsculas”). Cuando se llame, regresará una copia del string en la que todas las letras han sido convertido a mayúsculas. También hay toLowerCase (“a minúsculas”), que hace lo contrario.

Curiosamente, a pesar de que la llamada a toUpperCase no pasa ningún argumento, la función de alguna manera tiene acceso al string "Ouch", el valor de cuya propiedad llamamos. Cómo funciona esto se describe en el Capítulo 6.

Las propiedades que contienen funciones generalmente son llamadas metodos del valor al que pertenecen. Como en, “toUpperCase es un método de string”.

Este ejemplo demuestra dos métodos que puedes usar para manipular arrays:

let secuencia = [1, 2, 3];
secuencia.push(4);
secuencia.push(5);
console.log(secuencia);
// → [1, 2, 3, 4, 5]
console.log(secuencia.pop());
// → 5
console.log(secuencia);
// → [1, 2, 3, 4]

El método push agrega valores al final de un array, y el el método pop hace lo contrario, eliminando el último valor en el array y retornandolo.

Estos nombres algo tontos son los términos tradicionales para las operaciones en una pila. Una pila, en programación, es una estructura de datos que te permite agregar valores a ella y volverlos a sacar en el orden opuesto, de modo que lo que se agregó de último se elimine primero. Estas son comunes en la programación—es posible que recuerdes la pila de llamadas en el capítulo anterior, que es una instancia de la misma idea.

Objetos

De vuelta al Hombre-Ardilla. Un conjunto de entradas diarias puede ser representado como un array. Pero estas entradas no consisten en solo un número o un string—cada entrada necesita almacenar una lista de actividades y un valor booleano que indica si Jacques se convirtió en una ardilla o no. Idealmente, nos gustaría agrupar estos en un solo valor y luego agrupar estos valores en un array de registro de entradas.

Los valores del tipo objeto son colecciones arbitrarias de propiedades. Una forma de crear un objeto es mediante el uso de llaves como una expresión.

let dia1 = {
  ardilla: false,
  eventos: ["trabajo", "toque un arbol", "pizza", "salir a correr"]
};
console.log(dia1.ardilla);
// → false
console.log(dia1.lobo);
// → undefined
dia1.lobo = false;
console.log(dia1.lobo);
// → false

Dentro de las llaves, hay una lista de propiedades separadas por comas. Cada propiedad tiene un nombre seguido de dos puntos y un valor. Cuando un objeto está escrito en varias líneas, indentar como en el ejemplo ayuda con la legibilidad. Las propiedades cuyos nombres no sean nombres válidos de vinculaciones o números válidos deben estar entre comillas.

let descripciones = {
  trabajo: "Fui a trabajar",
  "toque un arbol": "Toque un arbol"
};

Esto significa que las llaves tienen dos significados en JavaScript. Al comienzo de una declaración, comienzan un bloque de declaraciones. En cualquier otra posición, describen un objeto. Afortunadamente, es raramente útil comenzar una declaración con un objeto en llaves, por lo que la ambigüedad entre estas dos acciones no es un gran problema.

Leer una propiedad que no existe te dará el valor undefined.

Es posible asignarle un valor a una expresión de propiedad con un operador =. Esto reemplazará el valor de la propiedad si ya tenia uno o crea una nueva propiedad en el objeto si no fuera así.

Para volver brevemente a nuestro modelo de vinculaciones como tentáculos—Las vinculaciones de propiedad son similares. Ellas agarran valores, pero otras vinculaciones y propiedades pueden estar agarrando esos mismos valores. Puedes pensar en los objetos como pulpos con cualquier cantidad de tentáculos, cada uno de los cuales tiene un nombre tatuado en él.

El operador delete (“eliminar”) corta un tentáculo de dicho pulpo. Es un operador unario que, cuando se aplica a la propiedad de un objeto, eliminará la propiedad nombrada de dicho objeto. Esto no es algo que hagas todo el tiempo, pero es posible.

let unObjeto = {izquierda: 1, derecha: 2};
console.log(unObjeto.izquierda);
// → 1
delete unObjeto.izquierda;
console.log(unObjeto.izquierda);
// → undefined
console.log("izquierda" in unObjeto);
// → false
console.log("derecha" in unObjeto);
// → true

El operador binario in (“en”), cuando se aplica a un string y un objeto, te dice si ese objeto tiene una propiedad con ese nombre. La diferencia entre darle un valor de undefined a una propiedad y eliminarla realmente es que, en el primer caso, el objeto todavía tiene la propiedad (solo que no tiene un valor muy interesante), mientras que en el segundo caso la propiedad ya no está presente e in retornara false.

Para saber qué propiedades tiene un objeto, puedes usar la función Object.keys. Le das un objeto y devuelve un array de strings—los nombres de las propiedades del objeto.

console.log(Object.keys({x: 0, y: 0, z: 2}));
// → ["x", "y", "z"]

Hay una función Object.assign que copia todas las propiedades de un objeto a otro.

let objetoA = {a: 1, b: 2};
Object.assign(objetoA, {b: 3, c: 4});
console.log(objetoA);
// → {a: 1, b: 3, c: 4}

Los arrays son, entonces, solo un tipo de objeto especializado para almacenar secuencias de cosas. Si evalúas typeof [], este produce "object". Podrias imaginarlos como pulpos largos y planos con todos sus tentáculos en una fila ordenada, etiquetados con números.

Representaremos el diario de Jacques como un array de objetos.

let diario = [
  {eventos: ["trabajo", "toque un arbol", "pizza",
            "sali a correr", "television"],
   ardilla: false},
  {eventos: ["trabajo", "helado", "coliflor",
            "lasaña", "toque un arbol", "me cepille los dientes"],
   ardilla: false},
  {eventos: ["fin de semana", "monte la bicicleta", "descanso", "nueces",
            "cerveza"],
   ardilla: true},
  /* y asi sucesivamente... */
];

Mutabilidad

Llegaremos a la programación real pronto. Pero primero, hay una pieza más de teoría por entender.

Vimos que los valores de objeto pueden ser modificados. Los tipos de valores discutidos en capítulos anteriores, como números, strings y booleanos, son todos inmutables—es imposible cambiar los valores de aquellos tipos. Puedes combinarlos y obtener nuevos valores a partir de ellos, pero cuando tomas un valor de string específico, ese valor siempre será el mismo. El texto dentro de él no puede ser cambiado. Si tienes un string que contiene "gato", no es posible que otro código cambie un carácter en tu string para que deletree "rato".

Los objetos funcionan de una manera diferente. Tu puedes cambiar sus propiedades, haciendo que un único valor de objeto tenga contenido diferente en diferentes momentos.

Cuando tenemos dos números, 120 y 120, podemos considerarlos el mismo número precisamente, ya sea que hagan referencia o no a los mismos bits físicos. Con los objetos, hay una diferencia entre tener dos referencias a el mismo objeto y tener dos objetos diferentes que contengan las mismas propiedades. Considera el siguiente código:

let objeto1 = {valor: 10};
let objeto2 = objeto1;
let objeto3 = {valor: 10};

console.log(objeto1 == objeto2);
// → true
console.log(objeto1 == objeto3);
// → false

objeto1.valor = 15;
console.log(objeto2.valor);
// → 15
console.log(objeto3.valor);
// → 10

Las vinculaciones objeto1 y objeto2 agarran el mismo objeto, que es la razon por la cual cambiar objeto1 también cambia el valor de objeto2. Se dice que tienen la misma identidad. La vinculación objeto3 apunta a un objeto diferente, que inicialmente contiene las mismas propiedades que objeto1 pero vive una vida separada.

Las vinculaciones también pueden ser cambiables o constantes, pero esto es independiente de la forma en la que se comportan sus valores. Aunque los valores numéricos no cambian, puedes usar una vinculación let para hacer un seguimiento de un número que cambia al cambiar el valor al que apunta la vinculación. Del mismo modo, aunque una vinculación const a un objeto no pueda ser cambiada en si misma y continuará apuntando al mismo objeto, los contenidos de ese objeto pueden cambiar.

const puntuacion = {visitantes: 0, locales: 0};
// Esto esta bien
puntuacion.visitantes = 1;
// Esto no esta permitido
puntuacion = {visitantes: 1, locales: 1};

Cuando comparas objetos con el operador == en JavaScript, este los compara por identidad: producirá true solo si ambos objetos son precisamente el mismo valor. Comparar diferentes objetos retornara false, incluso si tienen propiedades idénticas. No hay una operación de comparación “profunda” incorporada en JavaScript, que compare objetos por contenidos, pero es posible que la escribas tu mismo (que es uno de los ejercicios al final de este capítulo).

El diario del licántropo

Asi que Jacques inicia su intérprete de JavaScript y establece el entorno que necesita para mantener su diario.

let diario = [];

function añadirEntrada(eventos, ardilla) {
  diario.push({eventos, ardilla});
}

Ten en cuenta que el objeto agregado al diario se ve un poco extraño. En lugar de declarar propiedades como eventos: eventos, simplemente da un nombre de propiedad. Este es un atajo que representa lo mismo—si el nombre de propiedad en la notación de llaves no es seguido por un valor, su el valor se toma de la vinculación con el mismo nombre.

Entonces, todas las noches a las diez—o algunas veces a la mañana siguiente, después de bajar del estante superior de su biblioteca—Jacques registra el día.

So then, every evening at ten—or sometimes the next morning, after climbing down from the top shelf of his bookcase—Jacques records the day.

añadirEntrada(["trabajo", "toque un arbol", "pizza", "sali a correr",
          "television"], false);
añadirEntrada(["trabajo", "helado", "coliflor", "lasaña",
          "toque un arbol", "me cepille los dientes"], false);
añadirEntrada(["fin de semana", "monte la bicicleta", "descanso", "nueces",
          "cerveza"], true);

Una vez que tiene suficientes puntos de datos, tiene la intención de utilizar estadísticas para encontrar cuál de estos eventos puede estar relacionado con la transformación a ardilla.

La correlación es una medida de dependencia entre variables estadísticas. Una variable estadística no es lo mismo que una variable de programación. En las estadísticas, normalmente tienes un conjunto de medidas, y cada variable se mide para cada medida. La correlación entre variables generalmente se expresa como un valor que varia de -1 a 1. Una correlación de cero significa que las variables no estan relacionadas. Una correlación de uno indica que las dos están perfectamente relacionadas—si conoces una, también conoces la otra. Uno negativo también significa que las variables están perfectamente relacionadas pero que son opuestas—cuando una es verdadera, la otra es falsa.

Para calcular la medida de correlación entre dos variables booleanas, podemos usar el coeficiente phi (ϕ). Esta es una fórmula cuya entrada es una tabla de frecuencias que contiene la cantidad de veces que las diferentes combinaciones de las variables fueron observadas. El resultado de la fórmula es un número entre -1 y 1 que describe la correlación.

Podríamos tomar el evento de comer pizza y poner eso en una tabla de frecuencias como esta, donde cada número indica la cantidad de veces que ocurrió esa combinación en nuestras mediciones:

Eating pizza versus turning into a squirrel

Si llamamos a esa tabla n, podemos calcular ϕ usando la siguiente fórmula:

ϕ =
n11n00n10n01
n1•n0•n•1n•0

(Si en este momento estas bajando el libro para enfocarte en un terrible flashback a la clase de matemática de 10° grado—espera! No tengo la intención de torturarte con infinitas páginas de notación críptica—solo esta fórmula para ahora. E incluso con esta, todo lo que haremos es convertirla en JavaScript.)

La notación n01 indica el número de mediciones donde la primera variable (ardilla) es falso (0) y la segunda variable (pizza) es verdadera (1). En la tabla de pizza, n01 es 9.

El valor n1• se refiere a la suma de todas las medidas donde la primera variable es verdadera, que es 5 en la tabla de ejemplo. Del mismo modo, n•0 se refiere a la suma de las mediciones donde la segunda variable es falsa.

Entonces para la tabla de pizza, la parte arriba de la línea de división (el dividendo) sería 1×76−4×9 = 40, y la parte inferior (el divisor) sería la raíz cuadrada de 5×85×10×80, o √340000. Esto da ϕ ≈ 0.069, que es muy pequeño. Comer pizza no parece tener influencia en las transformaciones.

Calculando correlación

Podemos representar una tabla de dos-por-dos en JavaScript con un array de cuatro elementos ([76, 9, 4, 1]). También podríamos usar otras representaciones, como un array que contiene dos arrays de dos elementos ([[76, 9], [4, 1]]) o un objeto con nombres de propiedad como "11" y "01", pero el array plano es simple y hace que las expresiones que acceden a la tabla agradablemente cortas. Interpretaremos los índices del array como número binarios de dos-bits , donde el dígito más a la izquierda (más significativo) se refiere a la variable ardilla y el digito mas a la derecha (menos significativo) se refiere a la variable de evento. Por ejemplo, el número binario 10 se refiere al caso en que Jacques se convirtió en una ardilla, pero el evento (por ejemplo, “pizza”) no ocurrió. Esto ocurrió cuatro veces. Y dado que el 10 binario es 2 en notación decimal, almacenaremos este número en el índice 2 del array.

Esta es la función que calcula el coeficiente ϕ de tal array:

function phi(tabla) {
  return (tabla[3] * tabla[0] - tabla[2] * tabla[1]) /
    Math.sqrt((tabla[2] + tabla[3]) *
              (tabla[0] + tabla[1]) *
              (tabla[1] + tabla[3]) *
              (tabla[0] + tabla[2]));
}

console.log(phi([76, 9, 4, 1]));
// → 0.068599434

Esta es una traducción directa de la fórmula ϕ a JavaScript. Math.sqrt es la función de raíz cuadrada, proporcionada por el objeto Math en un entorno de JavaScript estándar. Tenemos que sumar dos campos de la tabla para obtener campos como n1• porque las sumas de filas o columnas no se almacenan directamente en nuestra estructura de datos.

Jacques mantuvo su diario por tres meses. El conjunto de datos resultante está disponible en la caja de arena para este capítulo, donde se almacena en la vinculación JOURNAL, y en un archivo descargable.

Para extraer una tabla de dos por dos para un evento en específico del diario, debemos hacer un ciclo a traves de todas las entradas y contar cuántas veces ocurre el evento en relación a las transformaciones de ardilla.

function tablaPara(evento, diario) {
  let tabla = [0, 0, 0, 0];
  for (let i = 0; i < diario.length; i++) {
    let entrada = diario[i], index = 0;
    if (entrada.eventos.includes(evento)) index += 1;
    if (entrada.ardilla) index += 2;
    tabla[index] += 1;
  }
  return tabla;
}

console.log(tablaPara("pizza", JOURNAL));
// → [76, 9, 4, 1]

Los array tienen un método includes (“incluye”) que verifica si un valor dado existe en el array. La función usa eso para determinar si el nombre del evento en el que estamos interesados forma parte de la lista de eventos para un determinado día.

El cuerpo del ciclo en tablaPara determina en cual caja de la tabla cae cada entrada del diario al verificar si la entrada contiene el evento específico que nos interesa y si el evento ocurre junto con un incidente de ardilla. El ciclo luego agrega uno a la caja correcta en la tabla.

Ahora tenemos las herramientas que necesitamos para calcular las correlaciónes individuales. El único paso que queda es encontrar una correlación para cada tipo de evento que se escribio en el diario y ver si algo se destaca.

Ciclos de array

En la función tablaPara, hay un ciclo como este:

for (let i = 0; i < DIARIO.length; i++) {
  let entrada = DIARIO[i];
  // Hacer con algo con la entrada
}

Este tipo de ciclo es común en JavaScript clasico—ir a traves de los arrays un elemento a la vez es algo que surge mucho, y para hacer eso correrias un contador sobre la longitud del array y elegirías cada elemento en turnos.

Hay una forma más simple de escribir tales ciclos en JavaScript moderno.

for (let entrada of DIARIO) {
  console.log(`${entrada.eventos.length} eventos.`);
}

Cuando un ciclo for se vea de esta manera, con la palabra of (“de”) después de una definición de variable, recorrerá los elementos del valor dado después of. Esto funciona no solo para arrays, sino también para strings y algunas otras estructuras de datos. Vamos a discutir como funciona en el Capítulo 6.

El análisis final

Necesitamos calcular una correlación para cada tipo de evento que ocurra en el conjunto de datos. Para hacer eso, primero tenemos que encontrar cada tipo de evento.

function eventosDiario(diario) {
  let eventos = [];
  for (let entrada of diario) {
    for (let evento of entrada.eventos) {
      if (!eventos.includes(evento)) {
        eventos.push(evento);
      }
    }
  }
  return eventos;
}

console.log(eventosDiario(DIARIO));
// → ["zanahoria", "ejercicio", "fin de semana", "pan", …]

Yendo a traves de todos los eventos, y agregando aquellos que aún no están en allí en el array eventos, la función recolecta cada tipo de evento.

Usando eso, podemos ver todos las correlaciones.

for (let evento of eventosDiario(DIARIO)) {
  console.log(evento + ":", phi(tablaPara(evento, DIARIO)));
}
// → zanahoria:      0.0140970969
// → ejercicio:      0.0685994341
// → fin de semana:  0.1371988681
// → pan:           -0.0757554019
// → pudin:         -0.0648203724
// and so on...

La mayoría de las correlaciones parecen estar cercanas a cero. Come zanahorias, pan o pudín aparentemente no desencadena la licantropía de ardilla. Parece ocurrir un poco más a menudo los fines de semana. Filtremos los resultados para solo mostrar correlaciones mayores que 0.1 o menores que -0.1.

for (let evento of eventosDiario(DIARIO)) {
  let correlacion = phi(tablaPara(evento, DIARIO));
  if (correlacion > 0.1 || correlacion < -0.1) {
    console.log(evento + ":", correlacion);
  }
}
// → fin de semana:           0.1371988681
// → me cepille los dientes: -0.3805211953
// → dulces:                  0.1296407447
// → trabajo:                -0.1371988681
// → spaghetti:               0.2425356250
// → leer:                    0.1106828054
// → nueces:                  0.5902679812

A-ha! Hay dos factores con una correlación que es claramente más fuerte que las otras. Comer nueces tiene un fuerte efecto positivo en la posibilidad de convertirse en una ardilla, mientras que cepillarse los dientes tiene un significativo efecto negativo.

Interesante. Intentemos algo.

for (let entrada of DIARIO) {
  if (entrada.eventos.includes("nueces") &&
     !entrada.eventos.includes("me cepille los dientes")) {
    entrada.eventos.push("dientes con nueces");
  }
}
console.log(phi(tablaPara("dientes con nueces", DIARIO)));
// → 1

Ese es un resultado fuerte. El fenómeno ocurre precisamente cuando Jacques come nueces y no se cepilla los dientes. Si tan solo él no hubiese sido tan flojo con su higiene dental, él nunca habría notado su aflicción.

Sabiendo esto, Jacques deja de comer nueces y descubre que sus transformaciones no vuelven.

Durante algunos años, las cosas van bien para Jacques. Pero en algún momento él pierde su trabajo. Porque vive en un país desagradable donde no tener trabajo significa que no tiene servicios médicos, se ve obligado a trabajar con a circo donde actua como El Increible Hombre-Ardilla, llenando su boca con mantequilla de maní antes de cada presentación.

Un día, harto de esta existencia lamentable, Jacques no puede cambiar de vuelta a su forma humana, salta a través de una grieta en la carpa del circo, y se desvanece en el bosque. Nunca se le ve de nuevo.

Arrayología avanzada

Antes de terminar el capítulo, quiero presentarte algunos conceptos extras relacionados a los objetos. Comenzaré introduciendo algunos en métodos de arrays útiles generalmente.

Vimos push y pop, que agregan y removen elementos en el final de un array, anteriormente en este capítulo. Los métodos correspondientes para agregar y remover cosas en el comienzo de un array se llaman unshift y shift.

let listaDeTareas = [];
function recordar(tarea) {
  listaDeTareas.push(tarea);
}
function obtenerTarea() {
  return listaDeTareas.shift();
}
function recordarUrgentemente(tarea) {
  listaDeTareas.unshift(tarea);
}

Ese programa administra una cola de tareas. Agregas tareas al final de la cola al llamar recordar("verduras"), y cuando estés listo para hacer algo, llamas a obtenerTarea() para obtener (y eliminar) el elemento frontal de la cola. La función recordarUrgentemente también agrega una tarea pero la agrega al frente en lugar de a la parte posterior de la cola.

Para buscar un valor específico, los arrays proporcionan un método indexOf (“indice de”). Este busca a través del array desde el principio hasta el final y retorna el índice en el que se encontró el valor solicitado—o -1 si este no fue encontrado. Para buscar desde el final en lugar del inicio, hay un método similar llamado lastIndexOf (“ultimo indice de”).

console.log([1, 2, 3, 2, 1].indexOf(2));
// → 1
console.log([1, 2, 3, 2, 1].lastIndexOf(2));
// → 3

Tanto indexOf como lastIndexOf toman un segundo argumento opcional que indica dónde comenzar la búsqueda.

Otro método fundamental de array es slice (“rebanar”), que toma índices de inicio y fin y retorna un array que solo tiene los elementos entre ellos. El índice de inicio es inclusivo, el índice final es exclusivo.

console.log([0, 1, 2, 3, 4].slice(2, 4));
// → [2, 3]
console.log([0, 1, 2, 3, 4].slice(2));
// → [2, 3, 4]

Cuando no se proporcione el índice final, slice tomará todos los elementos después del índice de inicio. También puedes omitir el índice de inicio para copiar todo el array.

El método concat (“concatenar”) se puede usar para unir arrays y asi crear un nuevo array, similar a lo que hace el operador + para los strings.

El siguiente ejemplo muestra tanto concat como slice en acción. Toma un array y un índice, y retorna un nuevo array que es una copia del array original pero eliminando al elemento en el índice dado:

function remover(array, indice) {
  return array.slice(0, indice)
    .concat(array.slice(indice + 1));
}
console.log(remover(["a", "b", "c", "d", "e"], 2));
// → ["a", "b", "d", "e"]

Si a concat le pasas un argumento que no es un array, ese valor sera agregado al nuevo array como si este fuera un array de un solo elemento.

Strings y sus propiedades

Podemos leer propiedades como length y toUpperCase de valores string. Pero si intentas agregar una nueva propiedad, esta no se mantiene.

let kim = "Kim";
kim.edad = 88;
console.log(kim.edad);
// → undefined

Los valores de tipo string, número, y Booleano no son objetos, y aunque el lenguaje no se queja si intentas establecer nuevas propiedades en ellos, en realidad no almacena esas propiedades. Como se mencionó antes, tales valores son inmutables y no pueden ser cambiados.

Pero estos tipos tienen propiedades integradas. Cada valor de string tiene un numero de metodos. Algunos muy útiles son slice e indexOf, que se parecen a los métodos de array de los mismos nombres.

console.log("panaderia".slice(0, 3));
// → pan
console.log("panaderia".indexOf("a"));
// → 1

Una diferencia es que el indexOf de un string puede buscar por un string que contenga más de un carácter, mientras que el método correspondiente al array solo busca por un elemento único.

console.log("uno dos tres".indexOf("tres"));
// → 8

El método trim (“recortar”) elimina los espacios en blanco (espacios, saltos de linea, tabulaciones y caracteres similares) del inicio y final de un string.

console.log("  okey \n ".trim());
// → okey

La función alcocharConCeros del capítulo anterior también existe como un método. Se llama padStart (“alcohar inicio”) y toma la longitud deseada y el carácter de relleno como argumentos.

console.log(String(6).padStart(3, "0"));
// → 006

Puedes dividir un string en cada ocurrencia de otro string con el metodo split (“dividir”), y unirlo nuevamente con join (“unir”).

let oracion = "Los pajaros secretarios se especializan en pisotear";
let palabras = oracion.split(" ");
console.log(palabras);
// → ["Los", "pajaros", "secretarios", "se", "especializan", "en", "pisotear"]
console.log(palabras.join(". "));
// → Los. pajaros. secretarios. se. especializan. en. pisotear

Se puede repetir un string con el método repeat (“repetir”), el cual crea un nuevo string que contiene múltiples copias concatenadas del string original.

console.log("LA".repeat(3));
// → LALALA

Ya hemos visto la propiedad length en los valores de tipo string. Acceder a los caracteres individuales en un string es similar a acceder a los elementos de un array (con una diferencia que discutiremos en el Capítulo 6).

let string = "abc";
console.log(string.length);
// → 3
console.log(string[1]);
// → b

Parámetros restantes

Puede ser útil para una función aceptar cualquier cantidad de argumentos. Por ejemplo, Math.max calcula el máximo de todos los argumentos que le son dados.

Para escribir tal función, pones tres puntos antes del ultimo parámetro de la función, asi:

function maximo(...numeros) {
  let resultado = -Infinity;
  for (let numero of numeros) {
    if (numero > resultado) resultado = numero;
  }
  return resultado;
}
console.log(maximo(4, 1, 9, -2));
// → 9

Cuando se llame a una función como esa, el parámetro restante está vinculado a un array que contiene todos los argumentos adicionales. Si hay otros parámetros antes que él, sus valores no seran parte de ese array. Cuando, tal como en maximo, sea el único parámetro, contendrá todos los argumentos.

Puedes usar una notación de tres-puntos similar para llamar una función con un array de argumentos.

let numeros = [5, 1, 7];
console.log(max(...numeros));
// → 7

Esto “extiende” al array en la llamada de la función, pasando sus elementos como argumentos separados. Es posible incluir un array de esa manera, junto con otros argumentos, como en max(9, ...numeros, 2).

La notación de corchetes para crear arrays permite al operador de tres-puntos extender otro array en el nuevo array:

Square bracket array notation similarly allows the triple-dot operator to spread another array into the new array:

let palabras = ["nunca", "entenderas"];
console.log(["tu", ...palabras, "completamente"]);
// → ["tu", "nunca", "entenderas", "completamente"]

El objeto Math

Como hemos visto, Math es una bolsa de sorpresas de utilidades relacionadas a los numeros, como Math.max (máximo), Math.min (mínimo) y Math.sqrt (raíz cuadrada).

El objeto Math es usado como un contenedor que agrupa un grupo de funcionalidades relacionadas. Solo hay un objeto Math, y casi nunca es útil como un valor. Más bien, proporciona un espacio de nombre para que todos estas funciones y valores no tengan que ser vinculaciones globales.

Tener demasiadas vinculaciones globales “contamina” el espacio de nombres. Cuanto más nombres hayan sido tomados, es más probable que accidentalmente sobrescribas el valor de algunas vinculaciones existentes. Por ejemplo, no es es poco probable que quieras nombrar algo max en alguno de tus programas. Dado que la función max ya incorporada en JavaScript está escondida dentro del Objeto Math, no tenemos que preocuparnos por sobrescribirla.

Muchos lenguajes te detendrán, o al menos te advertirán, cuando estes por definir una vinculación con un nombre que ya este tomado. JavaScript hace esto para vinculaciones que hayas declarado con let oconst pero-perversamente-no para vinculaciones estándar, ni para vinculaciones declaradas con var o function.

De vuelta al objeto Math. Si necesitas hacer trigonometría, Math te puede ayudar. Contiene cos (coseno), sin (seno) y tan (tangente), así como sus funciones inversas, acos, asin, y atan, respectivamente. El número π (pi)—o al menos la aproximación más cercano que cabe en un número de JavaScript—está disponible como Math.PI. Hay una vieja tradición en la programación de escribir los nombres de los valores constantes en mayúsculas.

function puntoAleatorioEnCirculo(radio) {
  let angulo = Math.random() * 2 * Math.PI;
  return {x: radio * Math.cos(angulo),
          y: radio * Math.sin(angulo)};
}
console.log(puntoAleatorioEnCirculo(2));
// → {x: 0.3667, y: 1.966}

Si los senos y los cosenos son algo con lo que no estas familiarizado, no te preocupes. Cuando se usen en este libro, en el Capítulo 14, te los explicaré.

El ejemplo anterior usó Math.random. Esta es una función que retorna un nuevo número pseudoaleatorio entre cero (inclusivo) y uno (exclusivo) cada vez que la llamas.

console.log(Math.random());
// → 0.36993729369714856
console.log(Math.random());
// → 0.727367032552138
console.log(Math.random());
// → 0.40180766698904335

Aunque las computadoras son máquinas deterministas—siempre reaccionan de la misma manera manera dada la misma entrada—es posible hacer que produzcan números que parecen aleatorios. Para hacer eso, la máquina mantiene algun valor escondido, y cada vez que le pidas un nuevo número aleatorio, realiza calculos complicados en este valor oculto para crear un nuevo valor. Esta almacena un nuevo valor y retorna un número derivado de él. De esta manera, puede producir números nuevos y difíciles de predecir de una manera que parece aleatoria.

Si queremos un número entero al azar en lugar de uno fraccionario, podemos usar Math.floor (que redondea hacia abajo al número entero más cercano) con el resultado de Math.random.

console.log(Math.floor(Math.random() * 10));
// → 2

Multiplicar el número aleatorio por 10 nos da un número mayor que o igual a cero e inferior a 10. Como Math.floor redondea hacia abajo, esta expresión producirá, con la misma probabilidad, cualquier número desde 0 hasta 9.

También están las funciones Math.ceil (que redondea hacia arriba hasta llegar al número entero mas cercano), Math.round (al número entero más cercano), y Math.abs, que toma el valor absoluto de un número, lo que significa que niega los valores negativos pero deja los positivos tal y como estan.

Desestructurar

Volvamos a la función phi por un momento:

function phi(tabla) {
  return (tabla[3] * tabla[0] - tabla[2] * tabla[1]) /
    Math.sqrt((tabla[2] + tabla[3]) *
              (tabla[0] + tabla[1]) *
              (tabla[1] + tabla[3]) *
              (tabla[0] + tabla[2]));
}

Una de las razones por las que esta función es incómoda de leer es que tenemos una vinculación apuntando a nuestro array, pero preferiríamos tener vinculaciones para los elementos del array, es decir, let n00 = tabla[0] y así sucesivamente. Afortunadamente, hay una forma concisa de hacer esto en JavaScript.

function phi([n00, n01, n10, n11]) {
  return (n11 * n00 - n10 * n01) /
    Math.sqrt((n10 + n11) * (n00 + n01) *
              (n01 + n11) * (n00 + n10));
}

Esto también funciona para vinculaciones creadas con let, var, o const. Si sabes que el valor que estas vinculando es un array, puedes usar corchetes para “mirar dentro” del valor, y asi vincular sus contenidos.

Un truco similar funciona para objetos, utilizando llaves en lugar de corchetes.

let {nombre} = {nombre: "Faraji", edad: 23};
console.log(nombre);
// → Faraji

Ten en cuenta que si intentas desestructurar null o undefined, obtendrás un error, igual como te pasaria si intentaras acceder directamente a una propiedad de esos valores.

JSON

Ya que las propiedades solo agarran su valor, en lugar de contenerlo, los objetos y arrays se almacenan en la memoria de la computadora como secuencias de bits que contienen las direcciónes—el lugar en la memoria—de sus contenidos. Asi que un array con otro array dentro de el consiste en (al menos) una región de memoria para el array interno, y otra para el array externo, que contiene (entre otras cosas) un número binario que representa la posición del array interno.

Si deseas guardar datos en un archivo para más tarde, o para enviarlo a otra computadora a través de la red, tienes que convertir de alguna manera estos enredos de direcciones de memoria a una descripción que se puede almacenar o enviar. Supongo, que podrías enviar toda la memoria de tu computadora junto con la dirección del valor que te interesa, pero ese no parece el mejor enfoque.

Lo que podemos hacer es serializar los datos. Eso significa que son convertidos a una descripción plana. Un formato de serialización popular llamado JSON (pronunciado “Jason”), que significa JavaScript Object Notation (“Notación de Objetos JavaScript”). Es ampliamente utilizado como un formato de almacenamiento y comunicación de datos en la Web, incluso en otros lenguajes diferentes a JavaScript.

JSON es similar a la forma en que JavaScript escribe arrays y objetos, con algunas restricciones. Todos los nombres de propiedad deben estar rodeados por comillas dobles, y solo se permiten expresiones de datos simples—sin llamadas a función, vinculaciones o cualquier otra cosa que involucre computaciones reales. Los comentarios no están permitidos en JSON.

Una entrada de diario podria verse así cuando se representa como datos JSON:

{
  "ardilla": false,
  "eventos": ["trabajo", "toque un arbol", "pizza", "sali a correr"]
}

JavaScript nos da las funciones JSON.stringify y JSON.parse para convertir datos hacia y desde este formato. El primero toma un valor en JavaScript y retorna un string codificado en JSON. La segunda toma un string como ese y lo convierte al valor que este codifica.

let string = JSON.stringify({ardilla: false,
                             eventos: ["fin de semana"]});
console.log(string);
// → {"ardilla":false,"eventos":["fin de semana"]}
console.log(JSON.parse(string).eventos);
// → ["fin de semana"]

Resumen

Los objetos y arrays (que son un tipo específico de objeto) proporcionan formas de agrupar varios valores en un solo valor. Conceptualmente, esto nos permite poner un montón de cosas relacionadas en un bolso y correr alredor con el bolso, en lugar de envolver nuestros brazos alrededor de todas las cosas individuales, tratando de aferrarnos a ellas por separado.

La mayoría de los valores en JavaScript tienen propiedades, las excepciones son null y undefined. Se accede a las propiedades usando valor.propiedad o valor["propiedad"]. Los objetos tienden a usar nombres para sus propiedades y almacenar más o menos un conjunto fijo de ellos. Los arrays, por el otro lado, generalmente contienen cantidades variables de valores conceptualmente idénticos y usa números (comenzando desde 0) como los nombres de sus propiedades.

Hay algunas propiedades con nombre en los arrays, como length y un numero de metodos. Los métodos son funciones que viven en propiedades y (por lo general) actuan sobre el valor del que son una propiedad.

Puedes iterar sobre los arrays utilizando un tipo especial de ciclo forfor (let elemento of array).

Ejercicios

La suma de un rango

La introducción de este libro aludía a lo siguiente como una buena forma de calcular la suma de un rango de números:

console.log(suma(rango(1, 10)));

Escribe una función rango que tome dos argumentos, inicio y final, y retorne un array que contenga todos los números desde inicio hasta (e incluyendo) final.

Luego, escribe una función suma que tome un array de números y retorne la suma de estos números. Ejecuta el programa de ejemplo y ve si realmente retorna 55.

Como una misión extra, modifica tu función rango para tomar un tercer argumento opcional que indique el valor de “paso” utilizado para cuando construyas el array. Si no se da ningún paso, los elementos suben en incrementos de uno, correspondiedo al comportamiento anterior. La llamada a la función rango(1, 10, 2) deberia retornar [1, 3, 5, 7, 9]. Asegúrate de que también funcione con valores de pasos negativos para que rango(5, 2, -1) produzca [5, 4, 3, 2].

// Tu código aquí.

console.log(rango(1, 10));
// → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.log(rango(5, 2, -1));
// → [5, 4, 3, 2]
console.log(sum(rango(1, 10)));
// → 55

Construir un array se realiza más fácilmente al inicializar primero una vinculación a [] (un array nuevo y vacío) y llamando repetidamente a su método push para agregar un valor. No te olvides de retornar el array al final de la función.

Dado que el límite final es inclusivo, deberias usar el operador <= en lugar de < para verificar el final de tu ciclo.

El parámetro de paso puede ser un parámetro opcional que por defecto (usando el operador =) tenga el valor 1.

Hacer que rango entienda valores de paso negativos es probablemente mas facil de realizar al escribir dos ciclos por separado—uno para contar hacia arriba y otro para contar hacia abajo—ya que la comparación que verifica si el ciclo está terminado necesita ser >= en lugar de <= cuando se cuenta hacia abajo.

También puede que valga la pena utilizar un paso predeterminado diferente, es decir -1, cuando el final del rango sea menor que el inicio. De esa manera, rango(5, 2) retornaria algo significativo, en lugar de quedarse atascado en un ciclo infinito. Es posible referirse a parámetros anteriores en el valor predeterminado de un parámetro.

Revirtiendo un array

Los arrays tienen un método reverse que cambia al array invirtiendo el orden en que aparecen sus elementos. Para este ejercicio, escribe dos funciones, revertirArray y revertirArrayEnSuLugar. El primero, revertirArray, toma un array como argumento y produce un nuevo array que tiene los mismos elementos pero en el orden inverso. El segundo, revertirArrayEnSuLugar, hace lo que hace el métodoreverse: modifica el array dado como argumento invirtiendo sus elementos. Ninguno de los dos puede usar el método reverse estándar.

Pensando en las notas acerca de los efectos secundarios y las funciones puras en el capítulo anterior, qué variante esperas que sea útil en más situaciones? Cuál corre más rápido?

// Tu código aquí.

console.log(revertirArray(["A", "B", "C"]));
// → ["C", "B", "A"];
let valorArray = [1, 2, 3, 4, 5];
revertirArrayEnSuLugar(valorArray);
console.log(valorArray);
// → [5, 4, 3, 2, 1]

Hay dos maneras obvias de implementar revertirArray. La primera es simplemente pasar a traves del array de entrada de adelante hacia atrás y usar el metodo unshift en el nuevo array para insertar cada elemento en su inicio. La segundo es hacer un ciclo sobre el array de entrada de atrás hacia adelante y usar el método push. Iterar sobre un array al revés requiere de una especificación (algo incómoda) del ciclo for, como (let i = array.length - 1; i >= 0; i--).

Revertir al array en su lugar es más difícil. Tienes que tener cuidado de no sobrescribir elementos que necesitarás luego. Usar revertirArray o de lo contrario, copiar toda el array (array.slice(0) es una buena forma de copiar un array) funciona pero estás haciendo trampa.

El truco consiste en intercambiar el primer y el último elemento, luego el segundo y el penúltimo, y así sucesivamente. Puedes hacer esto haciendo un ciclo basandote en la mitad de la longitud del array (use Math.floor para redondear—no necesitas tocar el elemento del medio en un array con un número impar de elementos) e intercambiar el elemento en la posición i con el de la posición array.length - 1 - i. Puedes usar una vinculación local para aferrarse brevemente a uno de los elementos, sobrescribirlo con su imagen espejo, y luego poner el valor de la vinculación local en el lugar donde solía estar la imagen espejo.

Una lista

Los objetos, como conjuntos genéricos de valores, se pueden usar para construir todo tipo de estructuras de datos. Una estructura de datos común es la lista (no confundir con un array). Una lista es un conjunto anidado de objetos, con el primer objeto conteniendo una referencia al segundo, el segundo al tercero, y así sucesivamente.

let lista = {
  valor: 1,
  resto: {
    valor: 2,
    resto: {
      valor: 3,
      resto: null
    }
  }
};

Los objetos resultantes forman una cadena, como esta:

Una lista vinculada

Algo bueno de las listas es que pueden compartir partes de su estructura. Por ejemplo, si creo dos nuevos valores {valor: 0, resto: lista} y {valor: -1, resto: lista} (con lista refiriéndose a la vinculación definida anteriormente), ambos son listas independientes, pero comparten la estructura que conforma sus últimos tres elementos. La lista original también sigue siendo una lista válida de tres elementos.

Escribe una función arrayALista que construya una estructura de lista como el que se muestra arriba cuando se le da [1, 2, 3] como argumento. También escribe una función listaAArray que produzca un array de una lista. Luego agrega una función de utilidad preceder, que tome un elemento y una lista y creé una nueva lista que agrega el elemento al frente de la lista de entrada, y posicion, que toma una lista y un número y retorne el elemento en la posición dada en la lista (con cero refiriéndose al primer elemento) o undefined cuando no exista tal elemento.

Si aún no lo has hecho, también escribe una versión recursiva de posicion.

// Tu código aquí.

console.log(arrayALista([10, 20]));
// → {valor: 10, resto: {valor: 20, resto: null}}
console.log(listaAArray(arrayALista([10, 20, 30])));
// → [10, 20, 30]
console.log(preceder(10, preceder(20, null)));
// → {valor: 10, resto: {valor: 20, resto: null}}
console.log(posicion(arrayALista([10, 20, 30]), 1));
// → 20

Crear una lista es más fácil cuando se hace de atrás hacia adelante. Entonces arrayALista podría iterar sobre el array hacia atrás (ver ejercicio anterior) y, para cada elemento, agregar un objeto a la lista. Puedes usar una vinculación local para mantener la parte de la lista que se construyó hasta el momento y usar una asignación como lista = {valor: X, resto: lista} para agregar un elemento.

Para correr a traves de una lista (en listaAArray y posicion), una especificación del ciclo for como esta se puede utilizar:

for (let nodo = lista; nodo; nodo = nodo.resto) {}

Puedes ver cómo eso funciona? En cada iteración del ciclo, nodo apunta a la sublista actual, y el cuerpo puede leer su propiedad valor para obtener el elemento actual. Al final de una iteración, nodo se mueve a la siguiente sublista. Cuando eso es nulo, hemos llegado al final de la lista y el ciclo termina.

La versión recursiva de posición, de manera similar, mirará a una parte más pequeña de la “cola” de la lista y, al mismo tiempo, contara atrás el índice hasta que llegue a cero, en cuyo punto puede retornar la propiedad valor del nodo que está mirando. Para obtener el elemento cero de una lista, simplemente toma la propiedad valor de su nodo frontal. Para obtener el elemento N + 1, toma el elemento N de la lista que este en la propiedad resto de esta lista.

Comparación profunda

El operador == compara objetos por identidad. Pero a veces preferirias comparar los valores de sus propiedades reales.

Escribe una función igualdadProfunda que toma dos valores y retorne true solo si tienen el mismo valor o son objetos con las mismas propiedades, donde los valores de las propiedades sean iguales cuando comparadas con una llamada recursiva a igualdadProfunda.

Para saber si los valores deben ser comparados directamente (usa el operador == para eso) o si deben tener sus propiedades comparadas, puedes usar el operador typeof. Si produce "object" para ambos valores, deberías hacer una comparación profunda. Pero tienes que tomar una excepción tonta en cuenta: debido a un accidente histórico, typeof null también produce "object".

La función Object.keys será útil para cuando necesites revisar las propiedades de los objetos para compararlos.

// Tu código aquí.

let objeto = {aqui: {hay: "un"}, objeto: 2};
console.log(igualdadProfunda(objeto, objeto));
// → true
console.log(igualdadProfunda(objeto, {aqui: 1, object: 2}));
// → false
console.log(igualdadProfunda(objeto, {aqui: {hay: "un"}, objeto: 2}));
// → true

Tu prueba de si estás tratando con un objeto real se verá algo así como typeof x == "object" && x != null. Ten cuidado de comparar propiedades solo cuando ambos argumentos sean objetos. En todo los otros casos, puede retornar inmediatamente el resultado de aplicar ===.

Usa Object.keys para revisar las propiedades. Necesitas probar si ambos objetos tienen el mismo conjunto de nombres de propiedad y si esos propiedades tienen valores idénticos. Una forma de hacerlo es garantizar que ambos objetos tengan el mismo número de propiedades (las longitudes de las listas de propiedades son las mismas). Y luego, al hacer un ciclo sobre una de las propiedades del objeto para compararlos, siempre asegúrate primero de que el otro realmente tenga una propiedad con ese mismo nombre. Si tienen el mismo número de propiedades, y todas las propiedades en uno también existen en el otro, tienen el mismo conjunto de nombres de propiedad.

Retornar el valor correcto de la función se realiza mejor al inmediatamente retornar falso cuando se encuentre una discrepancia y retornar verdadero al final de la función.