Skip to content

08. Cómo funciona JavaScript

Carlos Jaramillo edited this page Jun 27, 2020 · 9 revisions

Historia click aquí

  • 1995 Mocha (by Brendan Eich to Netscape in 2-3 weeks)
  • 1995 LiveScript (lo mismo a mocha, pero con más interactividad)
  • 1995 a finales, nace JavaScript (by Brendan Eich)
  • 1995 JScript (by Microsoft ingeniería inversa de Javascript)
  • 1997 ECMA
  • 2008 V8 enginee.
  • 2009 node.js
  • 2010 frameworks
  • 2015 ES6 (ECMA2015)

V8

Motor de JavaScript de Chrome (open source), Nace para correr mejor y más rápido google maps. Ahora hasta node corre en el backend con v8

Cuando se corre un archivo de javascript en el enginee, antes de comenzar a traducirlo, genera un entorno global (Main/Global Object called Window).

Global Environment:

  • Global Object (window)
  • this (variable, here this is window - global object)
  • Outer Environment

Execution Context:

  • Run Code:
    • Scan
    • Tokens
    • PreParser (optional)
    • Parser ( to find keywords (main keys - palabras reservadas de js) )
    • Interpreter
      • Profile (monitor) (optional) (try optimize code, and here it happens Hoisting)
        • compiler
        • Optimized code (returns bytecode)
    • Bytecode (Language to machine) (not is binary)

¿Como llegá un script al navegador?

El navegador interpreta el archivo HTML y cuando termina de transformarlo al DOM (Document Object Model - representación que hace el navegador de un documento HTML) se dispara el evento DOMContentLoaded el cual indica que el documento está disponible para ser manipulado. Todo script que carguemos en nuestra página tiene un llamado y una ejecución.

  • async Realiza la petición de forma asíncrona. Pero cuando esta acaba, detiene la carga del DOM para ejecutar el script.
  • defer Realiza la petición de forma asíncrona. Pero ejecuta el script cuando ya se cargó todo el documento
  • scripts embebidos: el browser carga linea a linea, cuando encuentra un código entre scripts detiene la carga para ejecutar el script.

Hay que tener en cuenta que cuando carga una página y se encuentra un script a ejecutar toda la carga se detiene. Por eso se recomienda agregar tus scripts justo antes de cerrar el body para que todo el documento esté disponible.

¿Qué hace un JS Engine?

Es Just in time compiler (JIT)

  • Recibe código fuente
  • Parsea el código y produce un Abstract Syntax Tree (AST)
  • Se compila a bytecode y empieza a ejecutarse
  • Se optimiza a machine code y se reemplaza el código base
-> JavaScript Source Code  - to:
  -> Parser  - to:
    -> Abstract Syntax Tree  - to:
            --Optimize--
-> Interpreter  - to:
  -> bytecode  - through 'Profiling data procesor' to:        //In Bytecode start to run
-> Optimizing Compiler  - to:
  -> Optimized code //Generate Machine Code

Parsers (Descomponer)

-> Código fuente
  -> Tokens //Parser
    -> Abstract Syntax Tree

Un SyntaxError es lanzado cuando el motor de javascript se encuentra con parte de código que no forman parte de la sintaxis del lenguaje al momento de analizar código (parser).

Notas google:

  • Parsing es 15-25% del proceso de ejecución
  • La mayoría del JS en una página nunca se ejecuta entonces el Bundling y Code Splitting son muy importantes

Parser de V8

Eager Parsing:

  • Encuentra errores de sintaxis
  • Crea el AST
  • Construye scopes

Lazy Parsing:

  • Doble de rápido que el eager parser
  • No crea el AST
  • Construye los scopes parcialmente

Tokens

Aquí o Aquí

var helloWorld = "Hello" + " World";
var answer = 6 * 7;
[
    {
        "type": "Keyword",
        "value": "var"
    },
    {
        "type": "Identifier",
        "value": "helloWorld"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "String",
        "value": "\"Hello\""
    },
    {
        "type": "Punctuator",
        "value": "+"
    },
    {
        "type": "String",
        "value": "\" World\""
    },
    {
        "type": "Punctuator",
        "value": ";"
    },
    {
        "type": "Keyword",
        "value": "var"
    },
    {
        "type": "Identifier",
        "value": "answer"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "Numeric",
        "value": "6"
    },
    {
        "type": "Punctuator",
        "value": "*"
    },
    {
        "type": "Numeric",
        "value": "7"
    },
    {
        "type": "Punctuator",
        "value": ";"
    }
]

Abstract Syntax Tree (AST)

El AST es un gráfo (estructura en forma de árbol). Donde vamos a tener una raíz que será nuestro programa y lo vamos a ir descomponiendo en partes, todo esto lo vamos a poder hacer siguiendo los tokens que produce el parser, esto se usa en muchas cosas, no solo para ejecutar un programa javascript, también lo usamos para transformar código de una forma a otra que es como lo que hace babel o prettier. Pruebalo Aquí

Se usa en:

  • Javascript Engine
  • Bundlers: Webpack, Rollup, Parcel
  • Transpilers: Babel
  • Linters: ESLint, Prettify
  • Type Checkers: TypeScript, Flow
  • Syntax Highlighters

Crear regla eslint:

const pi = 3.1415;
const halft_pi = 1.356;
// Variables constantes
// Variables que guarden un número 
// El nombre de la variable tiene que estar en UPPER CASE
export default function (context) {
    return {
        VariableDeclaration(node) {
            // Tipo de variable const
            // asegurarnos que el valor es un número  
            if (node.kind === "const" && typeof declaration.init.value === "number") {
                const declaration = node.declarations[0];
                if (declaration.id.name !== declaration.id.name.toUpperCase()) {
                    context.report({
                        node: declaration.id,
                        message: "El nombre de la constante debe estar en Mayúsculas",
                        //Opcional, autoarreglar el error
                        fix: function (fixer) {
                            return fixer.replaceText(declaration.id,
                                declaration.id.name.toUpperCase());
                        }
                    })
                }
            }
        }
    }
};

Qué hace un JS Engine

  • Recibe código fuente
  • Parsea el código y produce un Abstract Syntax Tree (AST)
  • Se compila a bytecode y se ejecuta
  • Se optimiza a machine code y se reemplaza el código base

Bytecode

  • Código parecido a assembly
  • Portatil
  • Ejecutado por una virtual machine

Machine Code

  • Binario
  • Instrucciones especificas a una arquitectura o procesador

Para aprovechar los motores de JavaScript:

  • Las funciones reciban los mismos tipos de datos
  • Las estructuras de datos (Arreglos y Objectos) mantengan la misma forma.

Así el motor de JS podrá optimizar el código que se ejecuta constantemente enviándolo de ByteCode a OptimizedCode.

function add(a, b) {
  return a + b;
}

for (let i = 0; i < 1000; i++) {
  add(i, i);
}

Se ira ejecutando el código hasta que esté preparado para ser optimizado, convirtiéndose en una Hot Function.

add(1,"hola");

Romperá la optimización y seguira ejecutando bytecode

Motor de diferentes navegadores

Event Loop

El bucle de eventos es un patrón de diseño que espera y distribuye eventos o mensajes en un programa.

Hace que js parezca ser multi-hilo a pesar de que corre en un solo proceso. Se encarga de preguntar o entender si la pila de ejecución (stack) está vacía, para así pasar tareas de las otras colas al stack

Js se organiza usando las siguientes estructuras de datos:

  • Stack (apilar): Apila de forma organizada las diferentes instrucciones que se llaman. Lleva así un rastro de dónde está el programa, en que punto de ejecución nos encontramos. (Almacena los primitivos dentro del scope, y las referencias a objetos)
  • Memory Heap (Memoria dinámica): Guarda información de las variables (objetos) y del scope de manera desorganizada.
  • Task Queue (cola de tareas - callback queue): Aquí se agregan las tares que ya están listas para pasar al stack y ser ejecutadas. El stack debe estar vacío para que esto suceda.
  • Schedule Tasks (tareas programadas): Aquí se agregan a la cola, las tareas programadas para su ejecución. (Web APIs: dom(document), timeout(setTimeout), ajax(XMLHttpRequest))
  • MicroTask Queue: Aquí se agregan las promesas. Esta Queue es la que tiene mayor prioridad.

El Event Loop es un loop que está ejecutando todo el tiempo y pasa periódicamente revisando las queues y el stack moviendo tareas entre estas dos estructuras.

function moreAsync() {
  console.log('start');
  setTimeout(() => console.log('setTimeout'), 0);
  Promise.resolve('Promise 1').then(msg => console.log(msg));
  Promise.resolve('Promise 2').then(msg => console.log(msg));
  console.log('end');
}
moreAsync();

Como es en pila, se saldrá el moreAsync del Stack y luego el anonymous, pasará el anonymous(del timeout) a task queue, promise 1 pasará al stack, luego promise2, y luego anonymous(task queue)

Código de ejecución

¿Qué sincronia es Javascript?

Single threed (Synchronous), porque javascript sólo puede hacer una tarea a la vez.

Memory Heap

Dónde se guardan los objetos (variables) y funciones (Todo lo que no sea un valor primitivo) en bloques de memoria de forma arbitraria y sin un orden, los cuales pueden ser usados multiples veces y sin una referencia única. Está almacenado por nodos. Entonces al momento de almacenar una variable/objeto lo encapsula y le asigna un id, y un id padre, a esto se le conoce como nodos. Lo que le permite al motor saber cuales son las variables y su contenido enlazado en caso que sea un objeto. Así el motor administra la memoria y este 'estante' es limpiado una vez termina la ejecución del JS.

Garbage Collection

Por defecto Javascript hace:

  1. Mark
  2. Sweep

Mark and sweep algorithm reduces the definition of "an object is no longer needed" to "an object is unreachable".

Pero igual con loops, o funciones, podemos llevar el memoryheap a un overstack, y creashea.

Call Stack

Pila de tareas a ejecutar, (LIFO) Last in, First Out. La primer tarea es el objeto global (window-this-anonymous)

// Inspector -> Sources -> Snippets -> New (código) -> A la derecha sale el Call Stack
function restarDos(num) {
  return num - 2;
}
function calcular() {
  const sumarTotal = 4 + 5;
  return restarDos(sumarTotal);
}
debugger; // aquí el call Stack sólo tiene el window, y luego se le añadirá calcular, luego restarDos
calcular();

Almacena los valores primitivos, dentro del scope (contexto de ejecución, de la función que tenga acceso a esa variable). Acceder al call stack es mucho más rápido que al Heap. Además en el CallStack, también se guardan las referencias (como si fueran valores primitivos). Cuando se asigna una variable a otra y esta apunta a un objeto, se copia la referencia, como si fuera un valor primitivo.

Si el objeto tiene atributos como un número por ejemplo, este se guarda en la posición de memoria reservada para ese objeto. Los objetos también pueden tener más objetos dentro. En ese caso, dentro de la posición de memoria de ese objeto se va a guardar una referencia a otra posición de memoria

Stack Overflow

Sobre flujo de tareas en el callStack. Por ejemplo hace 4 años más o menos, chrome implementó un bloqueo al llegar a cierto número de peticiones en el CallStack, parar de ejecutar un código. Antes se bloqueaba y se cerraba.

function overflow() {
   overflow();

overflow(); // Uncaught rangeError: Maximum call stack size exceeded

Clone this wiki locally