¿Cómo funciona JavaScript?: Engine? Runtime? Call Stack?

JavaScript, es sin lugar a dudas, el lenguaje de programación del momento, el lenguaje de programación que arrasó en diferentes rankings del 2017. Se encuentra dentro del TOP 5 como lenguaje de programación con mayor crecimiento, comunidad, proyección, demanda laboral y popularidad; grandes compañías tecnológicas lo tienen incluido en su stack, aprovechando el poder y soporte de este lenguaje para sus proyectos en distintos niveles: front-end, back-end, aplicaciones híbridas, dispositivos integrados y mucho más.

JavaScript es el lenguaje de programación con mayor crecimiento, amplia comunidad de programadores, buena proyección, alta demanda laboral y gran popularidad del 2017.

No es extraño ver proyectos, publicaciones y un gran número de preguntas y respuestas sobre JavaScript en sitios web como Github, StackOverflow y Quora.

Fuente: https://madnight.github.io/githut/

Como se muestra en las estadísticas de GitHub, JavaScript está en la parte superior en términos de Repositorios activos y Total Pushes en GitHub. No se queda atrás en muchas otras categorías tampoco.

Seguro que muchos usan JavaScript a diario, pero ¿realmente sabemos como funciona JavaScript? ¿Qué hay detrás de JavaScript?

Quizás hallas oído o leído sobre:

El Motor V8 de Javascript“, “JavaScript tiene un solo subproceso” o “JavaScript usa una cola de callback“.

En esta publicación, revisaremos estos conceptos y explicaremos de manera breve y directa cómo se ejecuta JavaScript. Al conocer estos conceptos, podrás tener una base más sólida a la hora de programar aplicaciones y sobre todo a considerar la programación de aplicaciones Non-Blocking.

El Motor JavaScript (JavaScript Engine)

El motor Javascript, es el software que interpreta el código JavaScript y que a su vez, ejecuta un script acorde a las instrucciones dadas. Todos los navegadores web tienen un motor JavaScript.

Dentro de los motores JavaScript más conocidos tenemos:

V8 Engine, creado por Google, es open source y utilizado por el navegador Google Chrome. Además de funcionar en este famoso navegador, también lo han adaptado para correr de lado servidor, haciendo que JavaScript sea utilizado como lenguaje de programación back-end (Node JS).

Repositorio Oficial: https://github.com/v8/v8

Chakra, creado por Microsoft, es utilizado por su navegador web Internet Explorer 9 y en su nuevo navegador web Microsoft Edge (incluido en sus Sistemas Operativos Windows 10). En el 2015, liberaron el código fuente de su motor JScript convirtiéndolo en open source.

Repositorio Oficial: https://github.com/Microsoft/ChakraCore

SpiderMonkey, creado por la Fundación Mozilla, es utilizado por su navegador web Mozilla Firefox.

Repositorio Oficial: SpiderMonkey Project.

Carakan, creado por Opera Software, utilizado por su navegador Opera.

De todos los JavaScript Engine anteriormente mencionados, el que goza de mayor popularidad es el motor V8 de Google. El motor V8 se usa dentro de Chrome y Node JS. Una vista de alto nivel del motor V8, es la siguiente.

El motor se compone de dos componentes principales:

Memoria (Memory Heap), encargado de la asignación de memoria.

Pila de Llamadas a Funciones (Call Stack), aquí es donde se encuentran las funciones a medida que se ejecuta el código.

El Runtime (Tiempo de Ejecución)

De seguro, como desarrollador utilizas muy a menudo diferentes APIs, por ejemplo “setTimeout()“. Estas APIs, sin embargo no son proporcionadas por el motor V8. Entonces, ¿De donde vienen? con la siguiente imagen, nos daremos una idea.

Adicional al motor JavaScript (en este ejemplo: Motor Chrome V8), tenemos las “Web APIs” que son provistas por los navegadores web, como DOM, AJAX, setTimeout y mucho más.

Y luego, tenemos el popular bucle de eventos (Event Loop) y la cola de funciones (Queue Callback)


La Pila de Llamadas de Funciones (Call Stack)

JavaScript es un lenguaje de programación de un único subproceso, lo que significa que tiene una sola Pila de llamadas a funciones (Call Stack). Por lo tanto, puede hacer una cosa a la vez.

El Call Stack es una estructura de datos que registra básicamente en qué parte del programa estamos. Si entramos en una función, la colocamos en la parte superior de la pila. Si regresamos de una función, salimos de la parte superior de la pila. Eso es todo lo que la pila puede hacer.

Veamos un ejemplo.

Cuando el motor comienza a ejecutar este código, la Pila de llamadas de Funciones (Call Stack) estará vacía. Luego, los pasos serán los siguientes:

Cada entrada en la Pila de llamada de Funciones (Call Stack) se llama “Stack Frame“.

En el siguiente ejemplo, se muestra como se lanza una excepción.

Si ejecutamos este código en Google Chrome (suponiendo que este código se encuentre en un archivo llamado BooGame.js), se generará el siguiente stack trace:

Overflowing: Esto sucede cuando alcanzas el tamaño máximo del Call Stack. Y eso podría suceder con bastante facilidad, especialmente si está utilizando funciones de manera recursiva sin probar el código de forma exhaustiva. Por ejemplo:

Cuando el motor comienza a ejecutar este código, comienza llamando a la función “run()“. Esta función, sin embargo, es recursiva y comienza a llamarse a sí misma sin condiciones de terminación. Entonces, en cada paso de la ejecución, la misma función se agrega a la Pila (Call Stack) una y otra vez. Se ve algo como esto:

Sin embargo, en algún momento, el número de llamadas de función en la Call Stack excede su tamaño, y el navegador decide tomar medidas, lanzando un error, que puede verse más o menos así:

Ejecutar código en un único subproceso puede ser bastante fácil, ya que no tiene que lidiar con escenarios complicados que surgen en entornos de subprocesos múltiples. Pero correr en un solo hilo también es bastante limitante. Como JavaScript tiene una sola Call Stack, ¿Qué sucede cuando tienes  funciones en la Call Stack que toman una gran cantidad de tiempo para ser procesadas? ¿Esto nos impacta?, la verdad Si y mucho.

El problema es que mientras la Call Stack tiene funciones para ejecutar, el navegador no puede hacer otra cosa: se bloquea. Esto significa que el navegador no puede procesar, no puede ejecutar ningún otro código, simplemente está bloqueado. Y esto crea problemas si quieres buenas UI (User Interfaces) fluidas en tu aplicación.

La mayoría de los navegadores ante este escenario de bloqueo,  generan un mensaje de error, preguntándole si desea finalizar la página web. ¿Se te hace familiar la siguiente imagen?

Entonces, ¿cómo podemos ejecutar código pesado sin bloquear la interfaz de usuario y hacer que el navegador no responda?. La solución es utilizar asynchronous callbacks. En los enlaces de referencia, les comparto un artículo completo sobre este tema.

Fuentes de referencia:

Buenas prácticas para programar con Javascript

Buenas Practicas de Programación

Les comparto unas básicas y buenas prácticas que debemos tener en cuenta a la hora de programar con Javascript.

1. No usar taquigrafía

Si bien puede resultar muy útil y rápido usar las distintas técnicas abreviadas para programar, debe tener mucho cuidado con algunas, por ejemplo, puedes tener algo similar a:

if (variableExiste)
    x = false;

Sin embargo considera esto:

if (variableExiste)
    x = false;
    llamarOtraFuncion();

Uno podría pensar que el código es equivalente a:

if (variableExiste) {
    x = false;
    llamarOtraFuncion();
}

Lo cual estaría mal. En realidad sería de la siguiente forma:

if (variableExiste) {
    x = false;
}
llamarOtraFuncion();

Si deseas omitir las llaves ({}) debería de ser cuando se trate de una sentencia de una sola línea, por ejemplo:

if (edad > 18) return "Es mayor de edad";

2. Utilice === en vez de ==

Javascript utiliza dos tipos diferentes de operadores de igualdad: === | !== y == | !=. Se considera una buena practica utilizar la primera forma.

Javascript Good Patterns dice lo siguiente: “If two operands are of the same type and value, then === produces true and !== produces false.”

Eso quiere decir que si dos operandos son del mismo tipo y valor, entonces === produce verdadero y !== produce falso.

3. Usa JSLint

JSLint es un depurador escrito por Douglas Crockford. Simplemente pega tu script, y la herramienta va a escanear rápidamente cualquier problema y errores que tuviera su código.

JSLint takes a JavaScript source and scans it. If it finds a problem, it returns a message describing the problem and an approximate location within the source. The problem is not necessarily a syntax error, although it often is. JSLint looks at some style conventions as well as structural problems. It does not prove that your program is correct. It just provides another set of eyes to help spot problems.”
– JSLint Documentation

4. Coloque su script al final de la página

Es una buena práctica que el código Javascript cargue después de haber cargado los elementos HTML de su página web.
Esto optimizará la velocidad en que se muestra su página web para el usuario. Cuando se carga un script, el navegador no puede continuar hasta que todo el archivo se haya cargado. De este modo, el usuario tendrá que esperar más tiempo antes de notar algún progreso.



Si usted tiene archivos JS cuyo único propósito es añadir funcionalidad – por ejemplo, después de hacer clic en un botón – debe poner los archivos en la parte inferior, justo antes de la etiqueta del cuerpo de cierre. Esto es absolutamente una buena práctica.

<!-- fin contenedor --><script type="text/javascript" src="archivoJavascript.js"></script><script type="text/javascript" src="funcionalidad.js"></script>

5. Declara las variables fuera de la sentencia FOR

Cuando ejecute sentencias FOR muy extensos, no haga que el motor trabaje mucho, por ejemplo:

for(var i = 0; i < algunArray.length; i++) {
   var contenedor = document.getElementById('contenedor');
   contenedor.innerHtml += 'mi numero: ' + i;
   console.log(i);
}

Observe cómo debemos determinar la longitud de la matriz para cada iteración, y cómo se recorre el DOM para encontrar el elemento “contenedor”, esto es muy ineficiente!

Quedaría mejor de esta forma:

var contenedor = document.getElementById('contenedor');
var longitud = algunArray.length;
var contenido = '';
for(var i = 0; i < longitud;  i++) {
   contenido += 'mi numero: ' + i;
   console.log(i);
}
contenedor.innerHtml = contenido;

6. La manera mas rápida de construir una cadena

Si tiene un array de cadenas y necesita armar una cadena puede optar por utilizar el método nativo join que le permite unir elementos de un array dentro de una cadena.

var ciudades = ["Lima", "Ica", "Arequipa", "Chiclayo"];
var lista = ciudades.join(",");
document.write(lista);

7. Reducir variables globales

Al reducir sus variables globales a una sola variable (a un solo nombre), reduce significativamente la posibilidad de malas interaciones con otras aplicaciones, widgets o librerias JS.
Por ejemplo, puedes tener el siguiente código.

var nombres = 'Gonzalo';
var apellidos = 'Chacaltana';
 
function hacerAlgo() {...}
 
console.log(nombres); // Gonzalo

Mejor puedes programar así:

var Persona = {
   nombres : 'Gonzalo',
   apellidos : 'Chacaltana',
   hacerAlgo : function() {...}
}
console.log(Persona.nombres); // Gonzalo

8. Comenta tu código

Puede parecer innecesario al principio, pero la verdad que es muy importante comentar nuestro código de la mejor manera posible. ¿Qué sucede cuando después de meses vuelves a revisar tu código, te darás cuenta que no puedes recordar fácilmente como era la línea de pensamiento o proceso que implementaste. O bien, ¿qué pasa si uno de tus compañeros tiene que revisar tu código? Lo mas probable que tarde un poco en entenderlo o quizás ni lo entienda. Por ello se recomienda que siempre, siempre comenten las secciones importantes de su código.

9. Usar {} en vez de New Object()

Existen muchas formas de crear objetos en Javascript, y quizás el método mas tradicional sea el siguiente:

var o = new Object();
o.nombres = 'Gonzalo';
o.apellidos = 'Chacaltana';
o.algunaFuncion = function() {
   console.log(this.nombres);
}

Sin embargo, este método se le conocer como una “mala practica” sin realmente serlo. En su lugar se recomienda utilizar el método literal que es mucho mas robusto que instanciar un Object().

var o = {
   nombres: 'Gonzalo',
   apellidos: 'Chacaltana',
   algunaFuncion : function() {
      console.log(this.nombres);
   }
};

Si solo necesita crear un objeto vacio, solo utilice las llaves {}

var o = {}

10. Usar [] en vez de New Array()

Lo mismo se aplica para crear un array.

var cursos = new Array();
a[0] = "Lenguaje de Programación";
a[1] = 'Administración de Proyectos';

Mejor, podríamos hacerlo de la siguiente forma:

var cursos = ['Lenguaje de Programación','Administración de Proyectos'];

11. Omitir var keyword para declarar muchas variables

var edad = 14;
var peso = 56;
var talla = 166;
var edad = 14,
    peso = 56,
    talla = 166;

Y por último y no menos importante, darse un tiempo para leer el libro Javascript Cookbook de Shelley Powers, un libro muy completo para profundizar en este grandioso lenguaje de programación.