Objetivos

  • El objetivo de esta unidad es mostrar los dos entornos de ejecución en los que se pueden ejecutar aplicaciones javascript: El navegador web y el sistema operativo a través de node.js. Al finalizar la unidad sabrás como ejecutar aplicaciones javascript en ambos entornos, habrás instalado node.js en tu computadora, y manejarás con fluidez npm: el gestor de paquetes de node.js. 

Videos de la unidad

A lo largo de la unidad se irán presentando diferentes videos. Aquí te ofrecemos la lista de reproducción con todos los videos de la unidad, para que los tengas más a mano.

Entornos de ejecución

¿Qué se entiende por entorno de ejecución?

«Entorno de ejecución» es uno de esos muchos conceptos de la informática que pertenece a la categoría de «díficiles de definir con precisión pero fáciles de entender en la práctica». Si queremos ser prácticos, basta con que  entendamos que un lenguaje de programación sirve de poco si no hay «algo» capaz de transformar el código en un conjunto de instrucciones que ponga a trabajar a la computadora como el programador haya dispuesto en dicho código.

Ese «algo» es lo que viene a ser el entorno de ejecución y no suele ser una sola cosa, sino varias. Los elementos que lo constituyen varían de un lenguaje a otro. Por lo general siempre es necesario un compilador, intérprete o máquina virtual y un conjunto de librerías propias del lenguaje. Pero puede constar de más elementos como un depurador o un bucle de eventos. Incluso en algunos casos puede contar como parte del entorno de ejecución el propio hardware (físico o virtual). Cada vez que leas o escuches la expresión «run time environment» o entorno de ejecución, piensa en todo aquello que transforma el código fuente de un lenguaje de programación en operaciones reales de una computadora.

Los entornos de ejecución de javascript

El nacimiento de javascript está íntimamente ligado al desarrollo de la web: el propósito original de este lenguaje fue enriquecer los documentos HTML con más dinamismo e iteractividad. Para lo cual se dotó al histórico navegador Netscape Communicator de un sencillo entorno de ejecución para un lenguaje que denominaron por cuestiones de marketing  javascript. De hecho, durante mucho tiempo, el único sitio donde una aplicación javascript se podía ejecutar era el navegador web.

A Brief History of JavaScript ofrece los hitos más importantes en la historia del lenguaje javascript.

Con el tiempo tanto el lenguaje como los entornos de ejecución de javascript han mejorado enormemente, relegando a un segundo lugar a otras alternativas, como Adobe Flash o los applets de Java que competían por el liderazgo de las aplicaciones que se ejecutan en un navegador web. La prueba clara es que todos los navegadores web más populares incorporan de serie un entorno de ejecución de javascript, pero no de Flash o de Java o de cualquier otro.

La potencia de javascript llegó al punto de que cualquier tipo de aplicación era posible… pero todas dentro del navegador web. Esta situación cambió cuando en 2009 Ryan Dahl, un intrépido y brillante desarrollador, pensó que un entorno de ejecución de javascript que funcionase directamente sobre el sistema operativo y diseñado según los mismos principios que los existentes para el navegador, sería capaz de competir con otros lenguajes y entornos, como Java o Pyhton, en la construcción de servidores y aplicaciones de tiempo real con manejo intensivo de datos (conocidas como DIRT: Data Intensive Real Time Applications). Perseveró en su idea y acertó. El proyecto se llamó node.js y con él javascript salió del navegador web con vocación de conquistar el mundo del sistema operativo, y convertirse en un lenguaje de propósito general.

La situación actual es que existen dos tipos de entornos en los que el código javascript se puede ejecutar, el «clásico» que funciona sobre el navegador web y el «revolucionario» que lo hace directamente sobre el sistema operativo. Lo bueno es que ambos son extremadamente parecidos en su diseño, lo que permite que aprender con fundamento a programar en javascript sobre el navegador web, sirve también para programar en javascript sobre el sistema operativo.

Ambos entornos de programación de javascript constan de:

un intérprete de javascript

Normalmente conocido como javascript engine, que implementa la especificación del lenguaje (Standard ECMA-262) y traduce el código javascript al lenguaje máquina que se ejecuta en el  procesador. Existen varios engines, pero el más utilizado actualmente es el conocido como V8 engine, desarrollador por el equipo de Google para los navegadores Chrome y Chromium y utilizado además por otros navegadores como Microsoft Edge e incluso por el entorno de ejecución sobre el sistema operativo Node.js. En este artículo puedes leer más sobre el V8 engine.

Unas librerías (API)

Las librerías da acceso desde el código javascript a funciones de entrada salida en el caso del sistema operativo o al DOM y a la  API web en el caso del navegador web.

Un mecanismo para la manipulación de eventos

Que se denomina bucle de eventos (event-loop) y que es la clave para entender los aspectos de programación asíncrona tan ampliamente usados cuando se programa con javascript.

Los timers

Son temporizadores que retrasan la ejecución de código hasta pasado un tiempo definido por el programador. También forman parte de la faceta asíncrona de javascript.

El siguiente gráfico muestra esquemáticamente las partes que constituyen el entorno de javascript tanto en el navegador web como en el sistema operativo. Observa lo parecido que son. Se diferencian tan solo en 2 detalles. Por una parte en el sistema operativo no hay que renderizar documentos HTML+CSS, solo se  necesita el intérprete javascript (engine). Por otro lado al no existir en el sistema operativo un HTML que renderizar, tampoco se necesita un DOM para su manipulación, pero si que se necesitan unas librerías para hacer llamadas de entrada/salida.

Entornos de ejecución javascript

Entornos de ejecución javascript

Es importante que el programador de javascript, aún cuando sólo vaya a dedicarse al desarrollo de aplicaciones web del lado del cliente, conozca ambos entornos de ejecución. La razón es que si tiene o quiere utilizar  algún framework, tendrá que habituarse a utilizar las aplicaciones CLI de construcción que forman parte todos estos framewoks. Y todas ellas están construidas con node.js

Node.js

Instalación de node.js

El instalador de node.js para los sistemas operativos más utilizados (MacOS, Windows y Linux) se puede descargar directamente desde la página oficial del proyecto node.js. La instalación por otro lado no ofrece ninguna complicación; como suele decirse, podría hacerlo un pollo picando sobre el ratón.

En los siguientes videos se muestra como se lleva a cabo en los sistemas operativos MacOS, Windows y Ubuntu (Linux).

Instalación de node.js en MacOS

 

Instalación de node.js en Windows

 

Instalación de node.js en Ubuntu

 

El vídeo de Ubuntu es mucho más largo que los demás pues se muestran 3 formas alternativas de realizar la instalación. Linux es un sistema operativo muy versátil que da mucho control al usuario. Por eso, y por que sabemos que es muy alta la probabilidad de que al usuario de Linux le encante «cacharrear», hemos decidido ir un poco más allá con este sistema.

La instalación añade dos ejecutables al sistema:

  • node, que es el entorno de ejecución en sí y,

  • npm, que es un gestor de librerías.

Estas dos aplicaciones se ejecutan en una interfaz de comando. Es decir, no cuentan con una interfaz gráfica de usuario. Hemos de teclear en la interfaz de comando que nos proporcione nuestro sistema operativo el comando node o npm seguido de las opciones pertinentes en función de nuestra intención.

Ejecución de una aplicación con node.js

Si queremos ejecutar el código de un fichero javascript basta con que lo pasemos como primer argumento del comando node.  Por ejemplo, si queremos ejecutar el siguiente código escrito en un archivo que hemos llamado prueba.js:

var a = 10;
var b = 20;
console.log(a + b)

Haremos:

$ node prueba.js

30

y listo!.

Veamos un ejemplo más elaborado con el fin de introducir algunos aspectos de la programación en javascript con node.js. Se trata de un código extraído de la documentación de node.js y que implementa una sencilla interfaz de comando que hace un eco (echo) de lo que el usuario escriba, y se despide amablemente cuando la cortamos.

const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'OHAI> '
});

rl.prompt();

rl.on('line', (line) => {
switch (line.trim()) {
  case 'hola':
    console.log('mundo!');
    break;
  default:
    console.log(`Qué dices? creo haber escuchado '${line.trim()}'`);
    break;
}
rl.prompt();
}).on('close', () => {
console.log('Que tengas buen día!');
process.exit(0);
});

Escribe con un editor de texto cualquiera (notepad, notepad++, vi, nano, etcétera) dicho código en un fichero llamado tyni_cli.js, ejecútalo con node.js, pruébalo y trata de entenderlo.

Descripción del funcionamiento del código anterior:

Importación de librerías
La primera línea es seguramente la que merece más atención por tratarse de un elemento propio del entorno de ejecución de node.js. Por ello si tratas de ejecutar este código en un navegador verás que no funciona.
El primer ejemplo al utilizar únicamente elementos propios del lenguaje javascript funciona tanto en node.js como en un browser.
En esta primera línea hacemos la importación de una librería de la API de node.js que sirve para leer cadenas desde algún dispositivo de entrada y volcarlas en uno de salida. Es decir, ofrece un acceso a una función de entrada/salida del sistema operativo.
Uso de la librería
La segunda declaración crea una variable que almacena un objeto capaz de leer líneas desde la entrada estándar (el teclado) y volcarlas a la salida estándar (la pantalla) pues son esos los dispositivos de entrada y salida que se han especificado en la creación del objeto (process.in y process.out).
A continuación con rl.prompt() se muestra la cadena “OHAI> “ con un cursor que espera alguna entrada desde el teclado seguida de enter para capturarla. Lo que técnicamente se conoce como prompt.
Callback en manejador de eventos
Las siguientes declaraciones definen el código que debe ejecutarse cuando se lanza el evento line, es decir cuando indicamos el final de la línea al pulsar la tecla enter, y cuando se lanza el evento close, es decir, cuando cerramos la aplicación mediante la combinación de teclas Ctrl+C. En el primer caso se hace un eco de la cadena de entrada, o se dice “mundo” si lo que escribimos es “hola”, y en el segundo se despide amablemente diciendo “Qué tengas un buen día!”.

El código anterior aunque sencillo y escueto muestra varias características de la programación en javascript sobre node.js: el uso de la API, la definición de respuestas a eventos mediante funciones de callback y el uso de instrucciones asíncronas como rl.prompt() que muestra el prompt y espera a que algo se teclee, pero no bloquea la ejecución del resto del código.

Un callback es una función que se pasa como argumento de otra función desde la cual es ejecutada. En javascript se usan habitualmente cuando realizamos algún tipo de proceso asíncrono. El ejemplo más típico es el de los manejadores de eventos de los elementos HTML. La función addEventListener(event, callback)  es un método que implementan todos los elementos del DOM y que tiene dos argumentos, el primero es el tipo de evento (click, por ejemplo) y el segundo es la función (callback) que ha de ejecutarse cuando ocurra el evento sobre el elemento. A la función de callback se le pasa como argumento el resultado de alguna operación anterior. En el caso del manejador de evento se pasa una variable con información sobre dicho evento (en el caso de click, se pasa la posición del ratón en el momento de hacer click)

La API de node.js

La API de node.js ofrece una enorme cantidad de funciones y objetos con los que podemos hacer prácticamente cualquier cosa que el sistema operativo permita. Un ejemplo muy típico en cualquier tutorial de node.js es el de un servidor web que responde a cualquier petición con un saludo.

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});

server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
})

La estructura es similar al código anterior; se importa el objeto de librería http, mediante el que se pueden construir servidores web y se crea una instancia que escuchará en la IP 127.0.0.1 y el puerto 3000 cuando es levantado mediante la función listen(). El servidor siempre contesta con la misma respuesta de código de estado 200, Content-Type texto plano y cuerpo “Hello, World!\n”.

Ejecución de una aplicación javascript con node.js

 

Uso y gestión de librerías externas: npm

Con la API propia de node.js y el lenguaje javascript podemos hacer cualquier aplicación CLI que se nos ocurra. Pero la vida como programadores nos resultará más fácil y productiva si utilizamos algunas de las miles de librerías externas que otros desarrolladores han construido y puesto libremente a la disposición de cualquier usuario de node.js.

Y es aquí donde entra en juego el comando npm; el gestor de librerías y dependencias. La salida del comando npm --help nos da una idea de que estamos ante una potente herramienta con muchas opciones y posibilidades, por lo que únicamente mostraremos lo más esencial para despegar.

En primer lugar tenemos que saber cómo se busca la librería que necesitamos para resolver el problema con el que tratamos. No hay duda de que alguien lo habrá resuelto antes que nosotros. Y si no es así, seguro que habrá algo parecido que nos facilite el trabajo. El buscador de la web de npm es la primera herramienta que nos ayuda a encontrar lo que buscamos. Y por supuesto cualquier buscador genérico como DuckDuckGo o Google y las miles de web sobre javascript y node.js nos servirán de inestimable ayuda para encontrar las librerías que mejor encajan para el desarrollo del proyecto.

Veremos con un ejemplo cómo se importan y utilizan las librerías externas gestionadas por npm. Supongamos que estamos trabajando en una aplicación en la que necesitamos monitorizar el hardware del equipo; el disco duro, la carga de CPU, el uso de la memoria etcétera. Con la API propia de node.js podremos hacer todo eso. Sin embargo es muy probable que alguien haya tenido este mismo problema y, también es bastante probable  que haya compartido su trabajo en el repositorio de librerías de node.js. Hacemos una búsqueda en la página de web de node.js y encontramos un montón de entradas. Después de trastear e investigar las distintas opciones, nos decantamos por una que se llama systeminfomation. En la página de dicha librería explican cómo instalarla y dan algún ejemplo de uso. Normalmente desde esa misma página se accede a la página web del proyecto en cuestión donde se suele encontrar más información sobre su uso y características.

Ahora creamos un nuevo directorio donde desarrollaremos la aplicación, y lo primero que haremos es iniciarlo como un proyecto node.js, lo que significa simplemente que añadimos un archivo especial denominado package.json cuyo fin es describir los metadatos del proyecto (autor, versión, url, etcétera) y definir sus dependencias, es decir, las librerías externas que usamos. Podemos crear el fichero package.json a mano, pero es más cómodo usar el comando:

$ npm init

Nos hace algunas preguntas para crear una primera versión del fichero package.json. Lo normal es que, a medida que desarrollamos la aplicación, esta primera versión se vaya modificando según vayamos añadiendo o eliminando dependencias (librerías) o cambiando o añadiendo algún metadato.

package name: (monitor_sistema) monitor_sistema
version: (1.0.0)
description: Un sencillo monitor del sistema
entry point: (index.js)
test command:
git repository:
keywords: system monitor
author: juanda rodríguez
license: (ISC) MIT
About to write to /Users/juanda/monitor_sistema/package.json:

{
"name": "monitor_sistema",
"version": "1.0.0",
"description": "Un sencillo monitor del sistema",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"system",
"monitor"
],
"author": "juanda rodríguez",
"license": "MIT"
}

Is this OK? (yes)

Y en el momento de confirmar se crea el archivo package.json con el contenido mostrado en la salida del comando npm init.

El siguiente paso consiste en añadir la librería systeminformation a nuestro proyecto.

$ npm install systeminformation --save

La opción --save sirve para que añada la librería a las dependencias del fichero package.json. Puedes y debes comprobar que esto es así.

Además puedes comprobar que se ha creado un directorio denominado node_modules y es ahí donde se ha desplegado la librería systeminformation y todas sus dependencias (si las hubiera). Ahora ya estamos en disposición de utilizar la librería en nuestro programa.

Creamos el archivo monitor.js y escribimos el siguiente código:

var sysinfo = require('systeminformation')

sysinfo.cpu()
.then(data => {
console.log("CPU");
console.log("---");
console.log(data);
})
.then(error => console.log(error));

sysinfo.mem()
.then(data => {
console.log("MEMORIA");
console.log("-------");
console.log(data);
})
.then(error => console.log(error));

Y lo ejecutamos con el comando node monitor.js. El programa es muy sencillo y obviamente mejorable. Solo intentamos mostrar cómo se cargan librerías externas. Como vemos, se hace exactamente igual que si fueran librerías propias de node.js; usando la función require(). Por defecto esta función busca las librerías en la API de node.js o en un directorio denominado node_modules ubicado en el directorio del proyecto. Por otro lado, definiendo la variable de entorno NODE_PATH puede ampliarse la lista de directorio donde require() debe buscar las librerías. Sin embargo normalmente esto no es necesario.

El autor de la librería decidió usar promesas para el diseño de sus funciones. Las promesas son un patrón de programación asíncrona muy usado en javascript. La idea es que la función en cuestión; sysinfo.mem() y sysinfo().cpu() en este caso, devuelve un tipo de objeto denominado Promise (promesa). Ese objeto hace una llamada asíncrona al sistema de entrada y salida, que se ejecuta en otro hilo distinto al del programa principal, por lo que este último sigue corriendo, no se bloquea. La promesa proporciona un método llamado then() al que se le pasa como argumento una función. Dicha función se denomina callback. La gracia del asunto es que cuando la llamada asíncrona se resuelve, el resultado se copia en el argumento de la función de callback y esta se ejecuta en el hilo principal del programa. Cuando ejecutas el programa anterior, es posible que se muestre antes la información de la memoria que la de la cpu, a pesar de que se ha llamado antes a la función sysinfo.cpu(). Este comportamiento es debido a la naturaleza asíncrona de las funciones de esta librería.

La programación en node.js da para un curso entero y más. Pero para trabajar con frameworks javascript como Angular estos conocimientos deberían bastar. Especialmente lo que se refiere a la instalación de nuevas librerías.

En los siguientes vídeos mostramos como importar módulos desarrollados tanto por nosotros como por terceros en node.js

Módulos propios. Importación de módulos externos desarrollados por nosotros

 

Módulos de terceros. Importación de módulos externos desarrollados por terceros

El navegador web

El otro entorno de ejecución de javascript, de hecho el genuino y más utilizado, es el browser. La principal diferencia con el entorno de node.js es la API. En el caso del browser la API está compuesta por una serie de funciones y objetos que se comunican con el propio browser. Es decir, no hay una comunicación directa con el sistema operativo. Este hecho permite que los programas javascript ejecutados en el browser no puedan hacer cualquier cosa, proporcionando el nivel de seguridad necesario para manejarse por un entorno inseguro por naturaleza como es la web, donde en cualquier momento podría llegar un programa con fines maliciosos. El nivel de seguridad es aún mayor dado que los procesos que se ejecutan en el browser están aislados unos de otros siguiendo una estrategia denominada sandboxing. Sin llegar a meternos en tales profundidades, nos quedaremos con la idea de que el browser proporciona un entorno de ejecución en el que un programa javascript no puede comunicarse directamente con el sistema operativo; solo puede hacerlo con los servicios del browser.

Cuando decimos que un proceso javascript que se ejecuta en el browser no puede hacer cualquier cosa, nos referimos a que no puede utilizar directamente los servicios de entrada/salida del sistema operativo. Por ejemplo no puede guardar archivos directamente, tiene que hacerlo a través del gestor de descargas.

Una parte fundamental y propia de la API del browser es el DOM (Document Object Model); una librería que representa a los documentos HTML mediante una estructura de árbol y mediante la cual podemos acceder y manipular cada elemento y atributo del documento HTML desde el código javascript. Es muy conocida entre los programadores javascript pues constituye la herramienta fundamental para poder dotar de dinamismo a las páginas web.

Por lo demás, el entorno de ejecución de javascript en el browser es prácticamente igual al de node.js; los componentes de programación asíncrona: el event loop y los timers, funcionan exactamente igual. Por ello, si el código javascript no utiliza funciones propias ni de la API del browser ni de la API de node.js, se podrá ejecutar sin problemas en ambos entornos de ejecución. Por eso se pueden construir librerías que son compatibles con ambos entornos. Una librería con funciones matemáticas, por ejemplo, no tiene por qué usar ni funciones del sistema operativo ni del browser, y sería perfectamente compatible con ambos entornos de ejecución.

Ejecución de una aplicación en el navegador web

Para ejecutar una aplicación javascript en un browser, esta debe ubicarse dentro de un documento HTML, que es lo que el browser sabe interpretar y renderizar. Existen varias maneras y estilos de incrustar el código javascript en un documento HTML.

En primer lugar podemos meterlo directamente en cualquier parte del documento utilizando el elemento <script> de HTML.

<html>
  <head>
  </head>

  <body>
      Hola, el resultado de la suma es: <span id="resultado"></span>
      <script>
          var a = 10;
          var b = 20;
          document.getElementById("resultado").innerHTML = a + b;
      </script>
</body>
</html>

En este ejemplo hemos utilizado el objeto document, que forma parte de la API del browser y que representa el DOM, para modificar el contenido del atributo <span> identificado por el nombre resultado con el resultado de sumar dos variables.

También podemos añadir los scripts en la sección head del documento HTML, pero si queremos modificar el DOM desde ahí, tenemos que esperar a que el documento completo se cargue y el DOM esté completamente construido.

<html>
<head>
  <script>
      var a = 10;
      var b = 20;
      document.addEventListener('DOMContentLoaded', function(){
          document.getElementById("resultado").innerHTML = a + b;
      });
     
  </script>
</head>
<body>
  Hola, el resultado de la suma es: <span id="resultado"></span>

</body>
</html>

El código es prácticamente el mismo pero hemos añadido un manejador de eventos al objeto document para que cuando el evento DOMContentLoaded se dispare (esto ocurre una vez que el DOM se ha construido completamente), se ejecute la función proporcionada como segundo argumento del manejador de eventos addEventListener.

Cuando el código de nuestra aplicación crece, es conveniente escribirlo en ficheros externos al documento HTML y referenciarlos con el atributo src del elemento <script>. Siguiendo el ejemplo anterior, escribimos el código javascript en un archivo que vamos a llamar suma.js:

// suma.js

var a = 10;
var b = 20;
document.addEventListener('DOMContentLoaded', function(){
  document.getElementById("resultado").innerHTML = a + b;
});

Y el documento HTML quedaría así:

<!-- index.html -->
<html>
  <head>
      <script src="./suma.js"></script>
  </head>

  <body>
      Hola, el resultado de la suma es: <span id="resultado"></span>
  </body>
</html>

Una aplicación javascript típica para el browser consta, por tanto, de uno o varios documentos HTML y uno o varios archivos javascript que son referenciados desde algún documento HTML a través del elemento <script>. Podemos añadir tantos elementos <script> como nuestra forma de organizar el código requiera.

Cuando programamos con Angular el problema de la organización del código está resuelto, pues el framework impone la estructura básica para organizar los archivos.

La ejecución del código anterior en un browser se puede hacer de dos maneras. La primera es abriendo el archivo desde el browser. Es decir desde el menú Archivo->Abrir archivo. Esta forma, aunque válida cuando el programa es sencillo, no es la más recomendada. La razón es que hay ciertas funcionalidades que sólo van bien cuando la aplicación se sirve desde un servidor web a través del protocolo HTTP, siendo esta la manera correcta de ejecutar una aplicación javascript en el browser.

El problema de esta segunda manera es que hay que disponer de un servidor web. Podemos instalar Apache, Nginx, Internet Information Service o cualquier otro, pero esto supone una complicación extra que podemos evitar si usamos un servidor web de desarrollo, más sencillo de instalar aunque no apto para ser usado en un entorno de producción.

Un ejemplo de tal servidor web de desarrollo es ws, que forma parte del catálogo de librerías y utilidades de npm y que se instala en nuestro sistema de la siguiente forma:

$ npm install -g local-web-server

La opción -g indica a npm que en lugar de desplegarse la librería localmente en el directorio en que estamos trabajando, lo haga globalmente a nivel de sistema. Esta forma de instalación global es útil y típica cuando la  librería que instalamos incluye ejecutables, como es el caso de local-web-server.

Una vez finalizada la ejecución, lanzamos el comando ws desde el directorio donde tenemos el documento HTML que deseamos servir y ya podemos acceder desde el browser a través de algunas de las url que indica la salida del comando ws.

$ ws
Serving at http://electron-2.local:8000, http://127.0.0.1:8000, http://172.16.100.103:8000

Más adelante veremos que Angular proporciona un servidor de desarrollo de este tipo para que el programador vaya probando el código que escribe.

Ejecución de una aplicación javascript en el browser

Ciclo de vida una aplicación javascript

¿Qué ocurre cuando se carga una aplicación en el navegador? Esto es lo que exploraremos en esta sección y la siguiente.

El proceso se puede dividir en dos fases. En la primera, denominada de construcción, tiene lugar el parseo del documento HTML y la ejecución de los scripts, y en la segunda la aplicación espera y responde a los eventos que:

  • suceden cuando el usuario interacciona con la aplicación a través de la interfaz gráfica,
  • ocurren debido a la interacción con los servidores cuando hace peticiones AJAX,
  • son disparador por los posibles timers declarados
Aunque en los vídeos hemos utilizado el entorno de javascript del navegador para explicar el ciclo de vida, las explicaciones referentes al manejo de eventos y timers valen tal cual para el caso de node.js. No ocurre lo mismo con la fase de construcción.
Código fuente. Este es el código que se ha utilizado para confeccionar los vídeos de esta sección.

Fase de construcción

En la fase de construcción a medida que se parsea el documento HTML, se va construyendo el DOM: un objeto con estructura de árbol que representa al documento HTML. La peculiaridad de esta fase es que cada vez que el navegador se encuentra con un elemento <script> se detiene la construcción del DOM y se ejecuta el código javascript contenido en dicho elemento.

Una vez construido el DOM, el navegador expone al intérprete de javascript dos objetos globales: el objeto window, que representa a la ventana donde se visualiza el DOM y el objeto document, que es el propio DOM.

El objeto window contiene todos los objetos y variables globales que se han declarado en los elementos <script> y también un conjunto de funciones de librerías conocida como la WebAPI. Por otro lado el objeto document proporciona los métodos necesarios para manipular y modificar el DOM.

En el caso de node.js no se lleva a cabo la construcción del DOM, pues la renderización del HTML es algo particular del navegador web, así que no hay objeto document en node.js. Sin embargo sí que existe algo parecido en concepto al objeto global window; se trata del objeto global. Este objeto, al igual que hace el objeto window en el navegador,  ofrece al interprete de javascript la API de entrada salida y los timers. Más allá de esa similitud, NO podemos equiparar la fase de construcción en el navegador y en node.js.

En el siguiente video se explican los detalles de la fase de construcción.

 Fase de manejo de eventos

Una vez finalizada la fase de construcción, comienza la fase de manejo de eventos durante la cual los manejadores de eventos que se hayan declarado en la fase de construcción se ejecutan en el momento en que ocurren sus eventos asociados. En esta fase entra en juego el bucle de eventos, un mecanismo fundamental de los entornos de ejecución de javascript.

En los siguientes video se explican los detalles de esta fase y un modelo para entender el funcionamiento del bucle de eventos.

Manejo de eventos I. Ejecución de una aplicación en el navegador: fase de manejo de eventos (I)
Manejo de eventos II. Ejecución de una aplicación en el navegador: fase de manejo de eventos (II)

Los timers

Los timers o temporizadores constituyen otro de los mecanismos asíncronos de los entornos de ejecución javascript.

Los timer funcionan de la misma manera en el navegador web y en node.js

La finalidad de los timers es posibilitar la ejecución de código un tiempo después de haber sido declarados. Para declarar un timer debemos proporcionar, por tanto el código que deseamos ejecutar y el lapso de tiempo después del cual queremos que se ejecute.

La API del navegador ofrece dos funciones principales, con dos comportamientos distintos:

  • setTimeout(funcion, tiempo), registra un timer que ejecuta la función dada en su primer argumento cuando pasa el tiempo indicado en su segundo argumento (en milisegundos). El tiempo se cuenta desde el momento en que se invoca la función setTimeout(). Por otro lado podemos eliminar temporizadores con la función clearTimeout().
  • setInterval(funcion, tiempo), registra un timer que ejecuta repetidamente la función dada en su primer argumento con el periodo de tiempo indicado en su segundo argumento (en milisegundos). El tiempo se cuenta desde el momento en que se invoca la función setInterval(). Por otro lado podemos eliminar temporizadores con la función clearInterval().

Transcurrido el tiempo indicado en el registro de los temporizadores, estos colocan a la función que deseamos ejecutar en la cola de eventos (también conocida como cola de callbacks). De manera que cuando le llegue el turno el bucle de eventos la colocará sobre el único hilo de ejecución javascript. La consecuencia es que, si la aplicación carga mucho este hilo por que existan funciones que requieran mucho tiempo para ejecutarse, tanto precisión de los temporizadores como la «reactividad» de la aplicación en general, se puede degradar notablemente.

Por tanto en el desarrollo de aplicaciones javascript es importante procurar que las funciones que se ejecutarán al ocurrir eventos y al dispararse los temporizadores (a estas funciones se les llama callbacks) no tarden demasiado tiempo en ejecutarse.

Si no hay más remedio que ejecutar algún código intensivo en el uso de CPU, lo ideal es utilizar webworkers en el caso del navegador o worker threads en el caso de node.js. Ambos mecanismos permiten la ejecución de código javascript en otro hilo distinto al principal del intérprete y son capaces de notificar al bucle de eventos cuando finaliza la ejecución devolviendo el resultado  que será procesado por la correspondiente función de callback.

En el siguiente vídeo mostramos cómo funcionan estos timers.

Qué hemos aprendido

  • El entorno de ejecución de javascript está constituido por un conjunto de mecanismos que hacen posible la ejecución de las aplicaciones javascript. Está constituido por un intérprete de javascript, un bucle de eventos, unas librerías, un bucle de evento y los timers.
  • Tanto en los navegadores web como en el sistema operativo (node.js) encontramos un entorno de ejecución de javascript. La diferencia entre ambos entornos estriba en la API que exponen al interprete javascript, en el caso del navegador la WebAPI y en el caso de node.js la librería de entrada y salida.
  • Las aplicaciones javascript se ejecutan en el entonor node.js sin más que invocar el comando node sobre el archivo javascript que deseamos ejecutar.
  • La importación de librerías externas en node se hace mediante la función require().
  • Podemos instalar y gestionar las librerías externas de terceros usando la herramienta npm, la cual nos sirve también para inicializar y gestionar nuestros proyectos node.js.
  • Las aplicaciones javascript se ejecutan sobre el browser incrustándo el código javascript en un documento HTML mediante el elemento <script>. Dicho código puede escribirse directamente sobre el documento HTML o existir en un archivo javascript que es referenciado con el atributo src del elememto <script>.
  • El ciclo de vida de una aplicación javascript consiste en dos fases: construcción y manejo de eventos. Ambas fases, aunque las hemos visto para el caso del navegador, funcionan de manera similar en el caso de node.js
  • Los timers permiten la ejecución de código de forma periódica o pasado un tiempo desde su declaración.