Objetos en Javascript

Objetos en Javascript

Primero quiero empezar con un poco de teoría y luego pasamos a los ejemplos, la idea es que este post contenga las bases para comenzar a trabajar con los objetos en javascript.

¿Que es un objeto en JavaScript?

Un objeto es cualquier elemento que no sea una primitiva, es decir que no sea de tipo string, number, Symbol, true, false, null o undefined. Incluso si los strings, numbers y boleanos no son objetos, se comportan como un objeto inmutable. Las cosas mas comunes que hacemos con los objetos es crearlos, actualizarlos, eliminarlos, hacer busquedas sobre ellos, enumerar sus propiedades, etc. Los objetos tienen propiedades, y estas propiedades tienen un nombre y un valor al que podemos acceder, tenemos que tener en cuenta que no pueden existir dos propiedades con el mismo nombre dentro del mismo objeto.

Propiedades

Dentro de las propiedades de los objetos podemos distinguir dos clases, las que definimos directamente y las que se heredan a través del objeto Prototype .

Adicionalmente del nombre y el valor, las propiedades tienen tres atributos:

  • El atributo writable que especifica si la propiedad puede ser seteada.
  • El atributo enumerable que especifica si el nombre de la propiedad es retornado en un loop for/in.
  • El atributo configurable que especifica que la propiedad puede ser eliminada y los atributos pueden ser alterados.

Muchos objetos nativos de JavaScript tienen propiedades de solo lectura, no enumerables o no configurables, sin embargo los objetos que nosotros creamos son modificables, enumerables y configurables.

¿Cómo definimos un objeto en JavaScript?

Primero, vamos a imaginar un personaje para un juego de rol, este personaje va a tener diferentes características como son, nombre, edad, raza (enano, elfo, mago, guerrero), salud, poderes, etc.

New Object()

let player = new Object();
player.name = "Frodo";
player.race = "Hobbit";
player.age = 20;
player.health = 100;

Object Literal

let player = {
  name: "Frodo",
  race: "Hobbit",
  age: 20,
  health: 100
}

Con los Objet Literal inicializamos un nuevo objecto cada vez que evaluamos ese elemento. El valor de las propiedades es evaluado cada vez que el literal es evaluado. Si un objeto es evaluado dentro de un for loop se crearán objetos por cada iteración.

Object.create()

Con esta opción creamos un nuevo objeto usando su primer argunmento como el prototipo del objeto.

let o1 = Object.create({x: 1, y: 2}); // o1 hereda x y y.
o1.x + o1.y // => 3

Si pasamos null el objeto no hereda ningún prototipo, el problema de esto es que no hereda ningún método base como el toString(), si queremos crear un objeto que tenga estos valores por defecto lo tenemos que crear de la siguiente forma:

let o3 = Object.create(Object.prototype);

¿Como accedemos a las propiedades de estos objetos?

Para obtener los valores de las propiedades de los objetos podemos usar la notación punto (.) o los corchetes ([]).

let name = author.surname; 
let title = author["surname"];

Si queremos crear una nueva propiedad, podemos hacer lo mismo

author.age = 30;
author["age"] = 30;

Algo muy importante a tener en cuenta, es que en la notación de corchetes, el valor es expresado como un string, los strings son tipos de datos de JavaScript, por ende pueden ser manipulados en tiempo de ejecución. Veamos un ejemplo:

let addr = "";
for(let i = 0; i < 4; i++) {
  addr += customer[`address${i}`] + "\n";
}

Este código lee y concatena las propiedades address0, address1, addreess2 y address3 del objeto.

Recorriendo un objeto

Para recorrer un objeto, disponemos de varias opciones, una es usar un for tradicional o una forma mas sencilla es usar un for...in.

function calculate(portfolio) {
  let total = 0.0;
  for(let stock in portfolio) { // Busca sobre los indices
    let shares = portfolio[stock]; // Obtiene el numero de shares
    let price = getQuote(stock); // revisa el precio del share
    total += shares * price; // suma el valor total al stock
  }
  return total; // retorna el valor total
}

Otras opciones que podemos usar son:

  • Object.keys(): retorna un array de nombres de las propiedades base enumerables (no las heredadas), tampoco retorna las propiedades de tipo Symbol.
  • Object.getOwnPropertyNames(): funciona como la anterior pero retorna un array de nombes de las propiedades base no enumerables junto con sus nombres como strings.
  • Object.getOwnPropertySymbols(): retorna las propiedades base que son de tipo Symbol sean o no enumerables.
  • Reflect.ownKeys(): retorna todas las propiedades base, enumerables, no enumerables, strings y Symbol.

Orden de enumeración de las propiedades

  • Las propiedades cuyo nombre son enteros no negativos se listan de primeras en orden numerico de menor a mayor. Esto significa que los arrays y los objetos de tipo array tendrán sus propiedades enumeradas en orden.
  • Despues de que todas las propiedades de tipo array son listadas, el resto de propiedades son listadas en el orden que fueron agregadas al objeto. Para las propiedades que son de tipo Object Literal se listan como aparecen en el literal.
  • Por último las propiedades de tipo Symbol se listan en el orden que fueron agregadas.

Eliminando propiedades

La forma mas sencilla para eliminar las propiedades de un objeto es usando el operador delete, pero hay que tener en cuenta que este operador solo elimina las propiedades que no son heredadas, tampoco puede remover las propiedades de tipo configurable que son false.

delete book.author; 
delete book["main title"]; 

delete Object.prototype // => false: esta propiedad es non-configurable
var x = 1; // Declaración de variable global
delete globalThis.x // => false: no se puede eliminar esta propiedad

Testear propiedades de los Objetos

En JavaScript tenemos dos métodos para verificar las propiedades de los objetos, hasOwnProperty() y propertyIsEnumerable()

let o = { x: 1 };
o.hasOwnProperty("x") // => true: o tiene la propiedad x
o.hasOwnProperty("y") // => false: o no tiene una propiedad y
o.hasOwnProperty("toString") // => false: toString es una propiedad heredada

Ahora si usamos propertyIsEnumerable() solo retorna true si la propiedad es una propiedad base y si su atributo enumerable es true.

let o = { x: 1 };
o.propertyIsEnumerable("x") // => true: o tiene la propiedad x
o.propertyIsEnumerable("toString") // => false: no es una propiedad base del objeto
Object.prototype.propertyIsEnumerable("toString") // => false: no es enumerable

Otra forma que podemos usar para verificar los objetos es haciendo un query sobre ellos, es decir:

let o = { x: 1 };
o.x !== undefined // => true: o tiene una propiedad x
o.y !== undefined // => false: o no tiene una propiedad y
o.toString !== undefined // => true: o hereda la propiedad toString

Ahora algo importante, si queremos recorrer el objeto sin que nos traiga las propiedades heredadas, si no unicamente las propiedades base del objeto, podemos usar lo siguiente:

let o = {x: 1, y: 2, z: 3};
for(let p in o) {
  if (!o.hasOwnProperty(p)) continue; // Evita las propiedades heredadas
}

for(let p in o) {
  if (typeof o[p] === "function") continue; // Evita todos los métodos
}

Extendiendo Objetos

Si usamos ES5 podemos extender un objeto de la siguiente manera:

let target = {x: 1}, source = {y: 2, z: 3};
for(let key of Object.keys(source)) {
target[key] = source[key];
}
target // => {x: 1, y: 2, z: 3}

Pero ya con ES6 tenemos la opción de usar Object.assign(), esta función espera dos o mas argumentos de tipo objeto.

let target = {x: 1}, source = {y: 2, z: 3};
let newElement = Object.assign(target, source) // => {x: 1, y: 2, z: 3}

Algo a tener en cuenta es que esta función copia las propiedades con las operaciones get y set, si un objeto fuente tiene un método get o el objeto destino tiene un método set, se invocarán durante la copia, pero no serán copiados. Si en uno de los objetos tenemos propiedades con valores por defecto asignados, pero esas propiedades no existen en el otro objeto, el resultado no será el esperado. Para evitar este problema podemos usar un objeto vacío:

o = Object.assign({}, defaults, o);

O usando el spread operator

o = {...defaults, ...o};

Bueno, por ahora esta es la primera parte, el tema es bastante extenso, en proximos posts voy a cubrir la serialización de objetos, conversión y otras cosas más.