Funciones en Node.js y Javascript. Más detalles

Funciones en Node.js y Javascript. Más detalles

“Funciones en node.js y Javascript. Más detalles”, es la segunda parte de lo más importante de las funciones en node.js y Javascript. El tema de funciones es muy importante, y también muy extenso. Aprenderás sobre el prototipo de una función, el comportamiento de la referencia a this, propiedad especial prototype, elevado de funciones y sus beneficios, variable adicional arguments y métodos útiles como call y apply. Y como extra muy importante, si llegas al final, las mejores recomendaciones de comunicación a través del código.

El prototipo de una función en Node.js y Javascript

En la primera parte de Funciones en Node.js y Javascript. Pudimos entender que las funciones son objetos, y el porqué las funciones son objetos. Una función tiene un prototipo, si no sabes que es un prototipo en Javascript, esto explica en esta publicación.

Las funciones tienen como prototipo a Function.prototype. Aunque a diferencia de los objetos literales no se ven tan clara la relación, podemos ejecutar la siguiente prueba.

function saludar() {
  return 'Hola';
}
saludar.__proto__ = Function.prototype; // true

El prototipo de Function.prototype es Object.prototype. Con esta última sentencia comprobamos lo que hemos dicho de los objetos en Javascript, tienen como objeto base a Object.prototype.

saludar.__proto__.proto === Object.prototype; // true;

Aquí el diagrama.

Cadena de prototipos de una función
Cadena de prototipos de una función

Tipos de invocaciones y referencia this

Las funciones en Node.js y Javascript se comportan de manera extraña al ser invocadas. Este comportamiento cambia según la forma en como se invoque e influye en como se trata la referencia this. En las siguientes cuatro secciones se explican estos tipos de invocaciones.

Invocación de funciones en Node.js y Javascript como método

Cuando una función se asigna a una propiedad de un objeto entonces decimos que es un método. Y cuando un método es invocado se puede hacer referencia al objeto al que pertenece a través de la palabra reservada this.

Como vimos en el tema de objetos, se puede acceder a una propiedad a través de corchetes, así ['nombreDePropiedad'].

const jaime = {
  nombre: 'Jaime',
  saludar: function() {
    return `Hola soy ${this.nombre}`;
  }
};
jaime.saludar(); // 'Hola soy Jaime'
jaime['saludar'](); // 'Hola soy Jaime'

Invocación de funciones en Node.js y Javascript como función

Cuando una función se invoca de esta manera y quiere hacer referencia a this, este debería obtener null o undefined, pero en lugar de eso, hace referencia al objeto global del ambiente donde se está ejecutando el código, en el caso del navegador web hace referencia a window. En el caso de node.js es el objeto global.

function saludar() {
  return this;
}
saludar(); // En el navegador es window y en node.js es global

Dentro del navegador web, es algo como lo siguiente.

Window {
  alert: ƒ alert()
  console: console {},
  fetch: ƒ fetch(),
  isNaN: ƒ isNaN(),
  clearInterval: ƒ clearInterval(),
  clearTimeout: ƒ clearTimeout(),
  setInterval: ƒ setInterval(),
  setTimeout: ƒ setTimeout()
  ...
}

Por otro lado en node.js es algo como esto.

<ref *1> Object [global] {
  global: [Circular *1],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  queueMicrotask: [Function: queueMicrotask],
  performance: [Getter/Setter],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  }
  ...
}

Ahora, si una función interna hace referencia a this.

function saludar() {
  function saludarInternamente() {
  	return this;
  }
  return saludarInternamente();
}
saludar(); // En el navegador web es window, en Node.js es global

Esta función interna sigue haciendo referencia al objeto window o global. Y lamentablemente este mismo comportamiento sucede aun si la función saludar es el método de un objeto.

const jaime = {
  nombre: 'Jaime',
  saludar: function() {
    function saludarInternamente() {
      return this;
    }
    return saludarInternamente();
  }
};
jaime.saludar();  // En el navegador web es window, en Node.js es global

Cuando el objeto se crea a través de una clase, la referencia a this es undefined, esto es una mejora, sin embargo, ¿Tendría más sentido que si hiciera referencia al objeto en cuestión? Yo opino que si, pero ese ejemplo vamos a guardarlo para cuando toquemos las clases.

Invocación de funciones en Node.js y Javascript como constructor

Javascript es un lenguaje de programación orientado a objetos basado en prototipos. Originalmente no usa clases, pero intenta simular una sintaxis basada en clases. Estamos hablando del operador new.

Las funciones constructoras son funciones que se invocan con el operador new para crear nuevos objetos. Como el ejemplo de abajo.

function Persona(nombre, edad) {
  this.nombre = nombre;
  this.edad = edad;
}
const jaime = new Persona('Jaime', 33);

Esta forma de crear objetos con una función constructora permite que la referencia a this esté correctamente enlazada al nuevo objeto. Lo podemos comprobar cuando ejecutamos las siguientes líneas.

jaime.nombre; // 'Jaime'
jaime.edad; // 33

Propiedad especial prototype

Existe una propiedad especial llamada prototype en las funciones, esta no es la misma propiedad oculta que enlaza a Function.prototype.

Esta propiedad especial prototype permite usar a una función constructora para simular la herencia clásica. Así cuando se crea un nuevo objeto usando la función constructora, este nuevo objeto pueda usar las propiedades de prototype. Esto propiedad básicamente funciona igual que las propiedades Object.prototype, Function.prototype, Array.prototype, y demás prototipos de objetos en Javascript. Con la diferencia, que nosotros agregamos el contenido manualmente a prototype. A continuación un ejemplo.

function Persona(nombre, edad) {
  this.nombre = nombre;
  this.edad = edad;
}
Persona.prototype.saludar = function () {
  return `Hola soy ${this.nombre} y tengo ${this.edad} años`;
};
Cadena de prototipos de una función y propiedad especial prototype
Cadena de prototipos de una función y propiedad especial prototype

Y con este ejemplo comprobamos que la función saludar invocada desde el objeto jaime, efectivamente tiene la referencia a this enlazada correctamente. Y puede acceder al nombre y edad del nuevo objeto jaime.

const jaime = new Persona('Jaime', 33);
jaime.saludar(); // Hola soy Jaime y tengo 33 años

Visualmente es como el siguiente diagrama.

Cadena de prototipos de un objeto creado con una función constructora
Cadena de prototipos de un objeto creado con una función constructora

Por último te preguntarás, ¿Por qué no agregamos los métodos dentro de la función constructora Persona? Muy parecido a lo que hicimos cuando vimos el tema de closure.

function Persona(nombre, edad) {
  this.nombre = nombre;
  this.edad = edad;
  this.saludar = function () {
    return `Hola soy ${this.nombre} y tengo ${this.edad} años`;
  };
}
const jaime = new Persona('Jaime', 33);
jaime.saludar(); // Hola soy Jaime y tengo 33 años

Pues la respuesta es que también se puede hacer, pero la desventaja es que estaríamos creando nuevas funciones cada vez que creamos una instancia de Persona. Con el uso de la propiedad prototype no se crean nuevas funciones, solo se hacen referencia a ellas, es decir, cada nuevo objeto debe ir a buscar en sus prototipos a la función saludar.

Nota importante sobre las funciones constructoras

Las funciones constructoras siempre empiezan con una letra mayúscula, no porque la sintaxis del lenguaje lo necesite, sino para que los programadores identifiquen a una función constructora de una que no lo es y se use adecuadamente el operador new.

Invocación de funciones en Node.js y Javascript con apply y call

Como ya hemos hecho mucho hincapié, las funciones en Node.js y Javascript son objetos. Por lo tanto una función también tiene métodos. Los métodos del prototipo Function.prototype.apply y Function.prototype.call, permiten definir explícitamente la referencia a this al ejecutarse el cuerpo de la función.

Invocación con Function.prototype.apply

const jaime = {
  nombre: 'Jaime',
  saludar: function(saludo) {
    return `${saludo}, soy ${this.nombre}`;
  }
};
jaime.saludar('Hola'); // Hola, soy Jaime
jaime.saludar.apply({ nombre: 'Pepito' }, ['Buenos días']); // Buenos días, soy Pepito

El método apply recibe como primer parámetro el objeto al que se va a enlazar la función y podrá hacer referencia usando this y como segundo parámetro un arreglo de valores que son los parámetros de la función. En este caso la función saludar solo recibe una cadena de caracteres a través del parámetro saludo. Es por eso que solo necesita un arreglo con un solo elemento.

Aquí un ejemplo con una función de dos parámetros.

function sumar(a, b) {
  return a + b;
}
sumar.apply(null, [1, 2]); // 3

En este ejemplo el primer parámetro del método apply es null porque la función no se enlaza a un objeto y no necesita usar la referencia this.

Invocación con Function.prototype.call

Este método es una simplificación del método apply, igualmente recibe dos parámetros, el objeto al cual se hará referencia con this y los argumentos de la función que va a invocar. La única diferencia es que en lugar de recibir un array de parámetros para la función que se va a invocar, recibe cualquier número de parámetros separados con comas. A continuación un ejemplo.

function sumar(a, b) {
  return a + b;
}
sumar.call(null, 1, 2); // 3

Parámetro extra arguments de las funciones en Node.js y javascript

Cuando una función es invocada, adicional a la lista de parámetros, tiene acceso a un parámetro llamado argument. Este parámetro extra contiene la lista de todos los parámetros pasados en la invocación de una función.

function saludar(complemento) {
  return arguments;
}
saludar('Hola');
/*
Arguments [
  0: "Hola",
  callee: ƒ saludar(saludo),
  length: 1,
  Symbol(Symbol.iterator): ƒ values(),
  [[Prototype]]: Object
]
*/

arguments es un objeto muy parecido a un Array, pero no lo es. En el ejemplo anterior vemos que su prototipo es Object.prototype y para ser un Array debería ser Array.prototype.

Debido a lo anterior, arguments no tiene todos los métodos útiles de un Array. Por ejemplo, que tal si queremos recorrer la lista de todos los parámetros, en un arreglo se podría usar el método Array.prototype.forEach, pero eso no es posible.

Una forma de mitigar este inconveniente es convertir el objeto arguments a un arreglo, usando el método Array.from. Y entonces si usar los métodos de Array.prototype que necesites. Ejemplo.

function saludar(complemento) {
  const args = Array.from(arguments);
  args.forEach(actual => console.log(actual));
}
saludar('Hola', 'Soy Jaime', 'Tengo 33 años');
/*
Hola
Soy Jaime
Tengo 33 años
*/

Aunque la función saludar solo recibe un parámetro, aun así todos los parámetros que se pasen en la invocación, se guardan en arguments.

Elevado de funciones en Node.js y JavaScript

El elevado se refiere a que la creación de función sube hasta el inicio del ámbito en donde se encuentre. Solo la declaración normal de función puede ser elevada. Ejemplo.

function saludar(complemento) {
  return agregarComplemento(complemento);
  function agregarComplemento(complemento) {
    return `Hola, ${complemento}`;
  }
}
saludar('soy Jaime'); // 'Hola, soy Jaime'

Como podemos ver la función agregarComplemento se puede utilizar antes de su definición, esto es porque javascript antes de ejecutar el código, crea la función al inicio del ámbito local donde se encuentra, el código anterior se traduce antes de ser ejecutado a lo siguiente.

function saludar(complemento) {
  function agregarComplemento(complemento) {
    return `Hola, ${complemento}`;
  }
  return agregarComplemento(complemento);
}
saludar('soy Jaime'); // 'Hola, soy Jaime'

Las funciones en forma de expresión y funciones flecha no cuentan con este elevado de funciones, así que es importante definirlas antes de ser usadas.

Beneficio del elevado de funciones

Este comportamiento parece confuso, pero cuando estás programando con funciones es muy útil. Cuando se programa se tiene funciones principales y funciones secundarias. Para la persona que lee nuestro código muy a menudo es suficiente con visualizar las funciones principales para entender el código, no tiene que perder el tiempo en ver los detalles de las funciones secundarias. Imaginate un archivo con cien líneas de código y compara encontrar las funciones principales en las líneas de inicio contra encontrar las funciones principales al final.

Veamos un ejemplo. Aquí al lector solo le interesa a alto nivel lo que renderiza renderVista.

function renderVista() {
  renderHeader();
  renderMain();
  renderFooter();
}
function renderHeader() {
  renderMenu();
  renderBanner();
  renderSearchForm()
  // ....
}
  function renderMenu() {
    // ...
  }
  function renderBanner()  {
    // ...
  }
  function renderSearchForm()  {
    // ...
  }
function renderMain() {
  renderContent();
  renderAside();
  // ...
}
  function renderContent() {
    // ...
  }
  function renderAside() {
    // ...
  }
  function renderFooter()  {
    // ...
  }

Es facil, ¿Verdad?, solo leemos la primera función e ignoramos el resto del código.

¿Pero qué pasa cuando ahora la función principal se encuentra al final? Ejemplo.

function renderMenu() {
  // ...
}
function renderBanner()  {
  // ...
}
function renderSearchForm()  {
  // ...
}
function renderHeader() {
  renderMenu();
  renderBanner();
  renderSearchForm()
  // ....
}
function renderMain() {
  renderContent();
  renderAside();
  // ...
}
function renderContent() {
  // ...
}
function renderAside() {
  // ...
}
function renderFooter()  {
  // ...
}
function renderVista() {
  renderHeader();
  renderMain();
  renderFooter();
}

Además de ir hasta el final para encontrar la función principal que nos interesa usar, se pierde el sentido de este archivo. O al menos tardamos más en entender el objetivo de este archivo, el cual es renderizar la vista.

Por último, si quisieras, puedes hacer que todo viva dentro de una sola función, utilizando funciones anidadas. Haciendo más claro la relación entre las funciones. Como el siguiente ejemplo.

function renderVista() {
  renderHeader();
  renderMain();
  renderFooter();
  function renderHeader() {
    renderMenu();
    renderBanner();
    renderSearchForm()
    // ....
      function renderMenu() {
      	// ...
      }
      function renderBanner()  {
        // ...
      }
      function renderSearchForm()  {
        // ...
      }
  }
  function renderMain() {
    renderContent();
    renderAside();
    // ...
      function renderContent() {
          // ...
      }
      function renderAside() {
        // ...
      }
  }
  function renderFooter()  {
    // ...
  }
}

¿Las funciones son referencia o valor?

Como ya vimos en la publicación sobre objetos, dado que la función es un objeto, entonces una función siempre será una referencia.

Diferencias importantes entre funciones flecha y tradicionales

Las funciones flechas tienen ciertas limitaciones si las comparamos con las funciones tradicionales, aunque hay que decir que si tu código está enfocado a la programación funcional, este tipo de funciones son muy convenientes.

Sus limitaciones en comparacion con las funciones tradicionales son:

  • No tienen el enlace correcto a this y no puede usar super().
    • Debido al punto anterior no funciona bien con los métodos call, apply y bind.
    • No pude usarse como método de un objeto.
  • No tiene la propiedad especial prototype.
    • Consecuencia del punto anterior no es posible usar una función flecha como función constructora.
  • No tiene acceso a parámetro extra arguments.

Veremos estas limitaciones en una futura publicación sobre las funciones flecha.

Comunicación con funciones en node.js y Javascript

Como ya lo hemos mencionado en otras publicaciones, el código es una forma de comunicación entre lo que creamos ahora con nuestro yo del futuro y nuestro equipo. Sabemos que la programación es una actividad social de mucha comunicación y ser eficientes en esta comunicación ahorra tiempo y dinero a programadores y no programadores.

Recordemos que las funciones son las unidades más pequeñas de organización de código. Además sin las funciones, ¿Cómo definiríamos el comportamiento de objetos y de funciones más grandes? Las funciones nos sirven para:

  • Organizar el código
  • Reusar código
  • Se usa para la composición de objetos, agregando comportamientos.
  • Se usa para la composición de funciones más complicadas

Teniendo en cuenta lo anterior, he aquí algunas recomendaciones a la hora de crear funciones.

Un muy, muy buen nombre

En esta publicación encontrarás las recomendaciones de nombres.

Lo más simple que se pueda

Una función debe ser lo más simple posible, y para eso debe tener una meta pequeña y específica, lo que conlleva a no complicar las cosas y hacer una cosa a la vez, paso a pasito, algo sencillo y bien hecho.

Cuando se resuelve un problema complicado, la forma recomendada de solucionarlo es como si imaginaras que eres un bebe, y tu problema es que no puedes correr. ¿Suena complicado no crees? Probablemente pudiste correr bien sin caerte alrededor de los tres años. Sin darte cuenta dividiste un problema grande en partes más pequeñas. Para llegar a correr, primero aprendiste a gatear, luego a caminar y finalmente intentaste correr con muchas caídas en el camino. Y si dividimos aún más estas tres fases encontraremos objetivos aún más pequeños, ejemplo, dar el primer paso. Así son las funciones, solucionan el problema pedacito por pedacito.

Siguiendo el enfoque anterior, una función simple es la que cumple con lo siguiente.

  • Se enfoca en resolver una sola cosa sencilla pero bien hecha.
  • Tendrá pocas líneas de código
  • Cuando mucho una o dos condiciones if o un switch
  • Cuando mucho uno o dos ciclos for, while, do while, etc.
  • Muy poquita indentación debido a los tres puntos anteriores
  • Por mucho tres parámetros, un humano solo puede recordar cuatro cosas a la vez, hagámosle la vida más fácil a nuestro compañero. Si de verdad necesitas muchos parámetros, puedes utilizar un objeto literal como único parámetro, así al menos no tenemos que recordar el orden en que deben de ir.
  • Todos los puntos anteriores lograrán que la función sea muy fácil de leer y comunicar.

En una futura publicación haremos una aplicación pequeña donde aplicaremos estas recomendaciones

Conclusiones

El tema de funciones es muy grande, aún nos faltan temas por ver. Como nos seguimos dando cuenta, las funciones son una pieza fundamental en Javascript y en el desarrollo de software en general. Según donde se invoque una función, podrá enlazarse al objeto this correcto. El elevado de funciones es beneficioso para la comunicación de las intenciones de nuestras funciones.

No olvidemos que las funciones son objetos. Las recomendaciones expuestas reflejan el principio ágil de simplicidad. Manteniendo las cosas simples, somos más productivos. En el caso de nuestro código, aumentamos el poder de flexibilidad y mantenimiento del mismo a través de una comunicación clara y código fácil de comprender.

Objetos en Node.js y Javascript. Lo realmente importante

Objetos en Node.js y Javascript. Lo realmente importante

Introducción

Objetos en Node.js y Javascript. Lo realmente importante” se refiere a los principios detrás de los objetos, lo que es realmente importante para continuar aprendiendo, mejorar el entendimiento de objetos en programación y en Node.js y Javascript.

Empecemos con el entendimiento de lo que es un objeto en programación.

¿Qué son los objetos en programación?

Se ha dicho mucho que la programación orientada a objetos representa el mundo real. La verdad es que la programación en general trata de representar el mundo real en algo digital. Así que la programación orientada a objetos también trata de representar digitalmente el mundo real, solo que con un poco más de énfasis en el uso de objetos. Pero como hemos establecido en esta publicación, lo importante NO son los objetos, lo importante es:

El paso de mensajes para la comunicación entre objetos

En programación un objeto es una forma digital de agrupar funcionalidades y que algunas de ellas pueden ser análogas a las de un objeto en la vida real, pero que es mucho más limitado y no se debe esperar que sean iguales, porque solo es una representación digital.

Cuando desarrollas software, y necesitas agregar una funcionalidad, modificar o arreglar un error, tu pensamiento lógico está basado en la representación de objetos digitales, sus relaciones y comunicaciones con otras entidades digitales. Nunca se debe pensar que como el objeto real se comporta de cierta manera, su representación también. Es de mucha ayuda conceptualmente, pero en la implementación muy a menudo son diferentes.

Recordemos lo que nos dice Alan Kay:

Lamento que hace tiempo haya acuñado el término Objetos para la programación porque hizo que las personas se enfocaran en la parte menos importante. La gran idea es «Envío de mensajes«

Alan Kay

También un objeto sirve para almacenar datos útiles que otros objetos pueden utilizar. Entonces un objeto nos sirve para:

  • Crear comunicaciones entre representaciones digitales de objetos.
  • Permite agrupar funcionalidades relacionadas con la representación digital del objeto.
  • Almacena datos que otro objeto puede usar. (Funciona como algunas estructuras de datos).

¿Qué son los objetos en Node.js y JavaScript?

Los objetos en Node.js y Javascript, son una colección de pares nombre: valor, parecido a los «arrays asociativos» de PHP. Estos pares de nombre/valor se les llama propiedades, una propiedad es como una variable y puede contener cualquier tipo de valor.

La clave del párrafo anterior es que puede contener cualquier tipo de valor, incluyendo funciones. Y con esto se identifican dos tipos de objetos.

  1. Objetos con funcionalidades y comunicaciones
    1. Que en su mayor parte tienen métodos para comunicarse con otros objetos y resolver problemas.
  2. Objetos con datos. También llamado estructura de datos
    1. Que en su mayor parte contiene datos y que nos sirven para almacenar y enviar información a través de mensajes. Estos objetos son utilizados por los objetos con funcionalidades y comunicaciones.

Recordemos que todo lo relacionado con objetos también funciona en cualquier lugar donde se ejecute JavaScript, node.js incluido.

¿Cómo crear objetos en Node.js y Javascript?

La forma más sencilla de crear un objeto es a través de un objeto literal. Un objeto literal es aquel que se crea con llaves {}, agregando sus propiedades y métodos. Como el siguiente ejemplo.

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime.getNombre(); // Jaime Cervantes Velasco

Tenemos un objeto con cinco propiedades, nombre, apellidoPaterno y apellidoMaterno son de tipo string, edad es de tipo number y la última propiedad, getNombre() es de tipo function. Aquí tienes más información sobre los tipos de datos.

Aunque más adelante veremos a detalle las funciones, de momento, podemos decir que las funciones nos permiten encapsular y ejecutar un conjunto de operaciones que hacemos muy a menudo. En lugar de repetir el código de estas operaciones muchas veces, mejor ponemos estas operaciones dentro de una función.

Cada vez que necesitemos ejecutar estas operaciones, simplemente ejecutamos la función, a esta ejecución de la función se le llama invocación. Ejemplo de invocación es en la última línea, donde utilizamos paréntesis, jaime.getNombre();.

¿Cómo obtengo el valor de una propiedad?

Para obtener alguna propiedad de un objeto, podemos hacer de dos maneras:

  • Notación de puntos
  • Usando corchetes, muy parecido a como se acceden a los datos de un Array

La primera forma, notación de puntos, es muy sencilla, ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime.nombre // 'Jaime'
jaime.apellidoPaterno; // 'Cervantes'
jaime.apellidoMaterno; // 'Velasco'
jaime.edad; // 33
jaime.getNombre // function ()  { return 'Jaime Cervantes Velasco'; }

La segunda forma, usando corchetes [], ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime['nombre'] // 'Jaime'
jaime['apellidoPaterno']; // 'Cervantes'
jaime['apellidoMaterno']; // 'Velasco'
jaime['edad']; // 33
jaime['getNombre'] // function ()  { return 'Jaime Cervantes Velasco'; }
jaime['getNombre']() // 'Jaime Cervantes Velasco'

Aquí accedimos a las propiedades con corchetes usando el nombre de las propiedades, la cuales son strings.

Destaca como invocamos a la función getNombre. Estas dos sentencias hacen lo mismo, invocar a la función getNombre:

jaime.getNombre();
jaime['getNombre']();

El nombre de propiedades son cadenas de caracteres

Cuando usamos la notación de corchetes, usamos una cadena de caracteres para obtener el valor. Lo que mencionamos antes sobre que Un objeto es una colección de pares nombre: valor. El nombre de todas las propiedades son cadena de caracteres (string).

Como los nombres de las propiedades son cadenas de caracteres, entonces podemos definir propiedades encerradas por comillas, como el siguiente ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  },
  'nueva-propiedad-con-guion-medio': 'Mi propiedad con guión medio encerrada por comillas'
};
jaime['nueva-propiedad-con-guion-medio']; // 'Mi propiedad con guión medio encerrada por comillas'

Y obtenerlos con notación de corchetes así, jaime['nueva-propiedad-con-guion-medio'].

Una propiedad como esta, no puede ser accedida con una notación de puntos, así jaime.nueva-propoiedad-con-guion.medio. Esta sentencia te dará un error en Javascript porque no es nombre de propiedad válido.

De hecho en Javascript el nombre de variables o propiedades válidos deben:

  • Empezar con una letra, incluido $ y _.
  • No puede empezar con números o caracteres usados en el lenguaje para otros propósitos, ejemplo -, %, /, +, &.
  • Después del la letra de inicio puedes ocupar números, otras letras válidas, $ y _.

¿Objetos en Node.js y Javascript que contienen otros objetos?

Un objeto puede contener cualquier tipo de dato, así que puede contener otros objetos. Veamos un ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  },
  direccion: {
    calle: 'Melchor Ocampo',
    numero: 2,
    colonia: 'Las Flores',
    municipio: 'Tezonapa',
    estado: 'Veracruz'
  }
};

Aquí agregamos un objeto direccion al objeto jaime. Simplemente usando las llaves para crear objetos literales.

¿Cómo agrego, modifico y elimino propiedades?

Una vez que creas un objeto, puedes agregar más propiedades, modificarlas y eliminarlas.

Agregar propiedades a los objetos en Node.js y Javascript

Se utiliza la asignación para agregar nuevas propiedades que no existen previamente.

const jaime = {
  nombre: 'Jaime',
};
jaime.apellidoPaterno = 'Cervantes';
jaime.appelidoMaterno = 'Velasco';
console.log(jaime ); // { nombre: 'Jaime', apellidoPaterno: 'Cervantes', apellidoMaterno: 'Velasco }

Actualizar propiedades a los objetos en Node.js y Javascript

Se utiliza también la asignación para actualizar el valor de las propiedades que ya existen.

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
};
jaime.apellidoPaterno = 'Perez'
jaime.apellidoMaterno = 'Moreno';
console.log(jaime ); // { nombre: 'Jaime', apellidoPaterno: 'Perez', apellidoMaterno: 'Moreno' }

Eliminar propiedades a los objetos en Node.js y Javascript

Para eliminar propiedades se usa el operador delete. Ejemplo:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
};
delete jaime.apellidoPaterno;
// o tambien así:
delete jaime['apellidoMaterno];
console.log(jaime); // { nombre: 'Jaime' }

Las propiedades apellidoPaterno y apellidoMaterno, ya no existe en el objeto jaime.

¿Objetos en Node.js y Javascript son una referencia o un valor?

Los objetos en Node.js y JavaScript siempre serán una referencia, es decir:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
};
const pedro = jaime;
pedro.nombre = 'Pedro';
jaime.nombre === pedro.nombre; // true;
jaime.nombre; // 'Pedro'
pedro.nombre; // 'Pedro'

Las constantes jaime y pedro hacen referencia al mismo objeto en memoria.

¿Cómo recorro las propiedades de un objeto?

Existen varias formas, la más simple de recorrer las propiedades de un objeto es la siguiente.

Tenemos el objeto jaime:

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};

Lo primero es obtener el nombre de sus propiedades con el método Object.keys, el cual genera un Array con los nombres de las propiedades.

const nombresProps = Object.keys(jaime);
console.log(nombresProps); // ["nombre", "apellidoPaterno", "apellidoMaterno", "edad", "getNombre"]

Ahora recorremos el arreglo nombresProps para obtener los valores.

nombresProps.forEach(nombreProp => {
  console.log(jaime[nombreProp]); // imprime el valor del nombre de propiedad actual
});

El resultado del código anterior es algo como lo siguiente.

'Jaime'
'Cervantes'
'Velasco'
33
function () {
  return 'Jaime Cervantes Velasco';
}

¿El prototipo de un objeto?

Javascript es un lenguaje de programación multi-paradigma, en su lado orientado a objetos, no utiliza clases para la reutilización de código, más bien usa composición de objetos. Pero ¿Cómo hace esta composición de objetos?

Mejor composición de objetos sobre herencia de clases

Mejor composición de objetos sobre herencia de clases

Design Patterns: Elements of Reusable Object-Oriented Software

Este principio es bastante aplicado en Javascript y por consiguiente se aplica de igual manera en Node.js, de hecho así fue construido. Todos los objetos en Javascript tiene un enlace a un prototipo, y puede utilizar todas las propiedades y funciones de su prototipo.

Este enlace de prototipo puede ser referenciado en código, usando la propiedad especial __proto__.

En el caso de los objetos literales, como jaime, son enlazados a Object.prototype, que es el objeto base de todos los objetos en Javascript.

const jaime = {
  nombre: 'Jaime',
  apellidoPaterno: 'Cervantes',
  apellidoMaterno: 'Velasco',
  edad: 33,
  getNombre: function () {
    return 'Jaime Cervantes Velasco';
  }
};
jaime.__proto__; // { constructor: f Object(), hasOwnProperty: f hasOwnProperty, ... }
jaime.__proto__ === Object.prototype; // true
jaime.toString === Object.prototype.toString; // true
jaime.toString(); //[object Object]

Si expandimos los prototipos en un navegador web como chrome, se visualiza de esta manera.

Objeto literal tiene como prototipo a Object.prototype
Objeto literal jaime tiene como prototipo a Object.prototype

Gráficamente, el enlace de prototipos es como la imagen de abajo.

Object.prototype el prototipo de jaime
Object.prototype el prototipo de jaime

El prototipo también lo puedes ver en node.js si lo enlazas a un debugger. Pero no veremos como hacer eso hoy.

¿Qué es la cadena de prototipos?

Hemos creado el objeto jaime literalmente, pero que tal si lo creamos a través de otro objeto persona, haciendo el objeto persona su prototipo inmediato.

const persona = {
  saludar: function () {
    return 'Hola';
  },
  comer: function () {
  	return 'comiendo...';
  }
};
const jaime = Object.create(persona); // Creamos jaime en base al prototipo persona

Si hacemos ahora un console.log(jaime):

console.log(jaime);

Y expandimos el resultado en un navegador web como chrome, el resultado se visualiza de la siguiente forma.

Cadena de prototipo
Cadena de prototipo

En la imagen, podemos ver que [[prototype]] apunta al objeto persona, por lo que jaime tiene acceso a los métodos comer y saludar del objeto persona.

Y si hacemos estas comparaciones vemos que el enlace al prototipo persona existe.

jaime.__proto__; // { saludar: f (), comer: f () }
jaime.__proto__  === persona; // true

Podemos invocar los métodos comer y saludar del objeto persona como si fueran de jaime.

jaime.comer();  // 'Comiendo...'
jaime.saludar(); // 'Hola'

Ahora si expandimos el último [[prototype]] ¿Cuál crees que sea el resultado?

Cadena de prototipos Object.prototype
Cadena de prototipos Object.prototype

Vemos claramente que el último [[prototype]] es Object.prototype, el prototipo base de todos los objetos en Javascript. Y puedes comprobarlo en código comparando Object.prototype con las propiedades especiales __proto__.

jaime.__proto__.__proto__ === Object.prototype; // true

También podemos usar el método toString como si fuera de jaime.

jaime.toString(); // [object Object]

A esto se le llama cadena de prototipos, un objeto puede tener los sub prototipos necesarios para reutilizar sus propiedades y métodos. En este caso fue así, jaime → persona → Object.prototype.

jaime --> persona --> Object.prototype
jaime –> persona –> Object.prototype

Peculiaridades con la cadena de prototipos

Usa los métodos y propiedades de los sub prototipos

El objeto jaime puede utilizar directamente las propiedades y métodos de sus prototipos.

jaime.toString(); // '[object Object]' --> de Object.prototype
jaime.hasOwnProperty('nombre'); // true --> de Object.prototype
jaime.saludar(); // 'Hola' --> de persona
jaime.comer(); // 'Comiendo...' --> de persona
jaime.nombre; // 'Jaime' --> de si mismo, objeto jaime

Actualizar propiedades no afecta a la cadena de prototipos

Cuando tenemos una propiedad que existe en varios sub prototipos, javascript usa el que esté más cerca en la cadena de prototipos. Supongamos que queremos cambiar el comportamiento del método toString en jaime así que en lugar de usar Object.prototype.toString, a jaime le agregamos un método con el mismo nombre.

Se puede decir que lo que estamos haciendo es actualizarlo.

jaime.toString = function () {
  return `${this.nombre} ${this.edad}`;
};
jaime.toString(); // 'Jaime 33'

Ahora se usa el método toString de jaime, no se recorre la cadena de prototipos hasta llegar a Object.prototype.toString como se hizo en los anteriores ejemplos. Por que ya existe un metodo toString directamente en nuestro objeto jaime.

El operador delete no elimina las propiedades de los prototipos

Cuando ocupamos el operador delete, este nunca toca la cadena de prototipos, solo elimina las propiedades propias del objeto en cuestión.

Si en la cadena de prototipos existe una propiedad con el mismo nombre de la que eliminamos, entonces ahora esa propiedad en la cadena de prototipos será utilizada.

Usando el ejemplo anterior, del método toString de jaime, si lo eliminamos con delete, entonces ahora el toString() de Object.prototype es el que se usara porque el objeto persona no tiene ese método directamente.

jaime.toString = function () {
  return `${this.nombre} ${this.edad}`;
};
jaime.toString(); // 'Jaime 33'
delete jaime.toString
jaime.toString(); // '[[object Object]]' --> De Object.prototype

En la última línea notamos que ahora jaime vuelve a usar el toString() de Object.prototype.

Ejemplos de objetos predefinidos en Node.js y Javascript

Como ya hemos mencionado en otras publicaciones, a excepción de los tipos primitivos, todo lo demás en javascript son objetos. En futuras publicaciones veremos más detalle de los objetos de tipo function y Array

¿Cómo crear un objeto function?

function imprimirPersona(persona) {
  console.log(persona.nombre);
  console.log(persona.edad);
}

Como la función imprimirPersona es un objeto, podemos agregarle propiedades sin ningún problema.

imprimirPersona.miPropiedad = 'Mi propiedad de una funcion';
console.log(imprimierPersona.miPropiedad); // 'Mi propiedad de una funcion

Las funciones son muy útiles, tan versátiles que con ellas podemos generar nuevos objetos, pero ese tema no pertenece a esta publicación.

¿Cómo crear un objeto Array?

La forma recomendada es simplemente crearlos usando corchetes [].

const vacio = [];
const numeros = [1, 2, 3, 4, 5, 6];
const animales = ['perro', 'gato', 'caballo'];
const conObjetos = [
  		{
          nombre: 'Jaime',
          edad: 33
        },
        function imprimirPersona(persona) {
          console.log(persona);
        }
	];

Como un array es un objeto, igual que una función, también le puedes agregar propiedades.

const numeros = [1, 2, 3, 4, 5, 6];
numeros.miPropiedad = 'Mi propiedad de un arreglo';
console.log(numeros.miPropiedad); // 'Mi propiedad de un arreglo

El Array es el objeto más claro para usarlo como estructura de datos.

¿Cómo crear un objeto Date?

const fecha = new Date();
console.log(fecha); // Fri May 28 2021 10:46:27 GMT-0500 (hora de verano central)

Como te podrás imaginar, al objeto date también le podemos agregar propiedades porque es un objeto

Conclusión

En Javascript y en cualquier plataforma donde se ejecute, un ejemplo es en node.js, tienen la peculiaridad de siempre tener un enlace a un prototipo. Así es como el lenguaje está diseñado. Ya sé que actualmente existen las clases, pero en realidad estas son funciones que internamente hacen uso de prototipos, también lo explicaremos en otra publicación.

Con excepción de los datos primitivos, todo lo demás en Javascript son objetos, es importante saber como funcionan para no atorarnos en este aprendizaje.

Con esta información tienes lo suficiente para seguir avanzando y utilizar los objetos más sabiamente.

El tema de los objetos es bastante extenso, todavía quedan muchas cosas a tomar en cuenta, pero ya es demasiada información que tenemos que dividirla en más publicaciones. Así que muy pronto continuaremos. Cualquier duda, no dudes en escribirla en los comentarios, ¡Estaremos contentos de ayudarte!.

es_MXES_MX