Overreacted

¿De qué está hecho JavaScript?

20 de diciembre de 2019 • ☕️☕️☕️ 14 min read

Durante mis primeros años programando en JavaScript, me sentía como si fuese un fraude. Aunque era capaz de crear páginas web usando frameworks, había algo que se me escapaba. No tenía una base sólida de JavaScript y por ese motivo, temía ir a entrevistas de trabajo.

Con el paso de los años, desarrollé un mapa mental con los fundamentos básicos de JavaScript y esto me dio confianza. Y ahora, quiero compartir una versión comprimida de todo ello. Está estructurado como un glosario, con cada tema resumido en unas pocas frases.

A medida que vayas leyendo, intenta puntuar tus conocimientos en relación a cómo de seguro te sientes acerca de cada tema. ¡No te juzgaré si no tienes conocimientos en todos ellos! Al final del post, hay algo que podría ayudarte si este es tu caso.


  • Valor: El concepto de valor es un poco abstracto. Es un “algo”. Un valor en JavaScript es lo mismo que un número a las matemáticas, o un punto en geometría. Cuando un programa se está ejecutando, su mundo está lleno de valores. Números como 1, 2 y 420 son valores, igual que lo son otras cosas, como esta frase: "Las vacas hacen mu". Aunque no todo es un valor, un número es un valor, pero una estructura de control if no lo es. A continuación, veremos algunos tipos de valores.

    • Tipos de valor: Existen varios “tipos” diferentes de valores. Por ejemplo, números como 420, cadenas de caracteres como "Las vacas hacen mu", objetos y algunos otros tipos más. Se puede ver el tipo de un valor poniendo typeof antes del valor. Por ejemplo, console.log(typeof 2) devuelve "number".
    • Valores primitivos: Algunos tipos de valores son “primitivos”. Se incluyen en esta categoría números, cadenas de caracteres y algunos otros tipos más. Una cosa peculiar de los valores primitivos es que no se pueden crear más tipos ni cambiarlos de ninguna manera. Por ejemplo, cada vez que escribes 2, obtienes el mismo valor 2. No puedes “crear” otro 2 en el programa, o hacer que el valor 2 se “convierta” en 3. Esto también es cierto para las cadenas de caracteres.
    • null y undefined: Estos dos, son tipos especiales de valores. Son especiales porque hay muchas cosas que no se pueden hacer con ellos — aunque a menudo causan errores. Por lo general, null representa la ausencia intencional de un valor, y undefined representa la ausencia involuntaria de un valor. Sin embargo, cuándo usarlos queda a disposición del programador. Existen porque a veces es mejor que una operación falle que proceder con la ausencia de un valor.
  • Igualdad: al igual que “valor”, igualdad es un concepto fundamental en JavaScript. Decimos que dos valores son iguales cuando son… en realidad, yo nunca diría esto. Si dos valores son iguales, significa que son el mismo valor. ¡No dos valores diferentes, sino uno! Por ejemplo, "Las vacas hacen mu" === "Las vacas hacen mu" y 2 === 2 porque 2 es 2. Ten en cuenta que usamos tres signos de igual para representar este concepto de igualdad en JavaScript.

    • Igualdad estricta: Igual que el ejemplo anterior.
    • Igualdad referencial: Igual que el ejemplo anterior.
    • Igualdad estándar: ¡Este es diferente! La igualdad estándar es cuando usamos dos signos de igual (==). Las cosas pueden ser consideradas más o menos iguales incluso si se refieren a valores diferentes que se parecen (por ejemplo, 2 y "2"). Se agregó a JavaScript desde el principio por conveniencia y desde entonces ha causado una confusión infinita. Este concepto no es fundamental, pero es una fuente común de errores. Puedes aprender cómo funciona en un día lluvioso, pero muchas personas intentan evitarlo.
  • Literal: Un literal es cuando nos referimos a un valor que escribimos literalmente en nuestro código. Por ejemplo, 2 es un literal de número, y "Banana" es un literal de cadena.
  • Variable: una variable permite referirse a un valor utilizando un nombre. Por ejemplo, let mensaje = "Las vacas hacen mu". Ahora podrías utilizar mensaje en lugar de repetir la misma frase cada vez que quieras usarla en el código. Más tarde, podrías cambiar mensaje para que apunte a otro valor, como mensaje = "Soy una morsa". Ten en cuenta que esto no cambia el valor en sí, sino solo donde apunta el mensaje, como si estuvieran “conectados”. Antes apuntaba a "Las vacas hacen mu", y ahora apunta a "Soy una morsa".

    • Alcance: apestaría si solo pudiera haber una variable ‘mensaje’ en todo el programa. En cambio, cuando definimos una variable, esta está disponible en una parte del programa. Esa parte se llama “alcance”. Existen reglas sobre cómo funciona el alcance, pero generalmente puedes buscar las llaves { y } más cercanas alrededor de donde una variable ha sido definida. Ese “bloque” de código es su alcance.
    • Asignación: cuando escribimos mensaje = "Soy una morsa", cambiamos la variable mensaje para que apunte al valor "Soy una morsa". Esto se denomina asignación, escritura o configuración de la variable.
    • let vsconst vs var: Por lo general, querrás usar let. Si deseas prohibir la asignación a una variable, puedes usar const. (A veces algunas bases de código o tus compañeros de trabajo pueden ser un poco puntillosos y obligan a usar ‘const’ cuando solo hay una asignación). Evita ‘var’ si puedes porque sus reglas de Scope son confusas.
  • Object: Un objeto es un tipo especial de valor en JavaScript. Lo bueno de los objetos es que pueden tener conexiones con otros valores. Por ejemplo, el objeto {sabor: "vainilla"} contiene la propiedad sabor que apunta al valor "vainilla". Piensa en un objeto como en un valor que es “tuyo” y tiene “conexiones desde él”.

    • Propiedad: Una propiedad es como una conexión desde un objeto apuntando a un valor. Puede que esto te recuerde a una variable: tiene un nombre (como sabor) y apunta a un valor (como "vainilla"). Pero al contrario que una variable, una propiedad “vive” en en el propio objeto en lugar de en algún lugar del código (alcance). Una propiedad se considera parte de un objeto — pero el valor al que apunta no es parte de ese objeto.
    • Objeto Literal: Un Objeto Literal es una manera de crear un valor en un objeto literalmente escribiéndolo en el código, como {} o {sabor: "vainilla"}. Dentro de {}, podemos tener múltiples pares propiedad: valor separados por comas. Esto nos permite indicar hacia donde “conecta” la propiedad desde nuestro objeto.
    • Identidad del Objecto: ya mencionamos que 2 es igual que 2 (en otras palabras, 2 === 2) porque cuando escribimos 2, estamos “llamando” al mismo valor. ¡Pero cuando escribimos {}, siempre recibiremos un valor diferente! Entonces {} no es igual a otro {}. Intenta esto en la consola: {} === {} (el resultado es false). Cuando el ordenador se encuentra 2 en tu código, siempre nos devuelve el mismo valor 2. Sin embargo, los objetos literales son diferentes: cuando el ordenador se encuentra {}, crea un nuevo objeto, el cual es siempre un valor nuevo. Entonces, ¿qué es la identidad del objeto? Es simplemente otro término para igualdad, o igualdad de valores. Cuando decimos “a y b tienen la misma identidad”, queremos decir que “a y b apuntan al mismo valor” (a === b). Cuando decimos “a y b tienen diferentes identidades”, queremos decir “a y b apuntan a valores diferentes” (a !== b).
    • Notación por punto: Cuando queremos leer o asignar una propiedad a un objeto, podemos usar la notación por punto (.). Por ejemplo, si la variable helado apunta a un objeto en el que la propiedad sabor apunta a "chocolate", escribir helado.sabor te devolvera "chocolate".
    • Notación por corchetes: A veces no sabemos el nombre de la propiedad que queremos acceder. Por ejemplo, puede que unas veces queramos leer helado.sabor y otras helado.gusto. La notación por corchetes ([]) nos permite leer una propiedad cuando su propio nombre es una variable. Por ejemplo, digamos que let miPropiedad = 'sabor'. Luego helado[miPropiedad] nos devolverá "chocolate". Curiosamente, también podemos usarlo cuando creamos objetos: { [miPropiedad]: "vainilla" }.
    • Mutación: Decimos que un objeto a sido mutado cuando alguien cambia para que apunte a un valor diferente. Por ejemplo, si declaramos let helado = {sabor: "vainilla"}, podemos después mutarlo con helado.sabor = "chocolate". Observa que incluso cuando usamos const para declarar helado, todavía podremos mutar helado.sabor. Esto es porque const solo previene asignaciones a la variable helado, mientras que lo que nosotros hemos mutado es la propiedad (sabor) del objeto al que apunta. Algunas personas renuncian a usar ‘const’ por completo porque les resulta demasiado confuso.
    • Array: Un array es un objeto que representa una lista de “cosas”. Cuando creamos un literal de array como ["banana", "chocolate", "vainilla"], esencialmente estamos creando un objeto cuya propiedad llamada 0 apunta al valor "banana", la propiedad llamada 1 apunta al valor "chocolate", y la propiedad llamada 2 apunta al valor "vainilla". Sería un rollo escribir {0: ..., 1: ..., 2: ...} y por esto mismo es por lo que los arrays son muy útiles. Hay varias maneras de trabajar con arrays que vienen incorporadas a JavaScript por defecto, como map, filter, y reduce. No desesperes si reduce parece muy confuso — es confuso para cualquiera.
    • Prototipo: ¿Qué pasa si intentamos acceder a una propiedad que no existe? Por ejemplo, helado.gusto (cuando la propiedad en realidad se llama sabor). La respuesta es que recibiremos el valor especial undefined. Una respuesta más detallada sería que la mayoría de los objetos en JavaScript tienen un “prototipo”. Puedes pensar en el prototipo como si fuese una propiedad “escondida” en cada objeto que determina “donde continuar buscando”. Así que si no existe la propiedad gusto en helado, JavaScript buscará la propiedad gusto en el prototipo del objeto, después en el prototipo de este objeto, y así sucesivamente, y solo retornará undefined si llega hasta el final de la “cadena de prototipos” sin encontrar .gusto. Es poco común interactuar con este mecanismo directamente, pero explica por qué el objeto helado tiene el método toString aunque nunca lo hayamos definido nosotros mismos — es porque viene del prototipo.
  • Funciones: Una función es un valor especial que tiene un objetivo concreto: representa un fragmento de código del programa. Las funciones son muy útiles para no tener que escribir el mismo código repetidas veces. “Llamar” a una función como saludo() le dice al ordenador que ejecute el código que contiene la función y que luego vuelva al mismo punto donde se encontraba antes. Hay muchas maneras de crear una función en JavaScript, con pequeñas diferencias en lo que puede hacer cada una.

    • Argumentos (o Parámetros): Los argumentos nos permiten pasar información a la función en el momento en que la llamamos: saludo("Amelie"). Dentro de la función, los parámetros actúan como variables. Se les llama “argumentos” o “parámetros” dependiendo de donde nos encontremos (si es donde definimos la función o donde la llamamos). Sin embargo, esta distinción es muy puntillosa, y en la práctica estos términos se usan indistintamente.
    • Expresiones de función: Previamente, hemos declarado una variable con un valor cadena de caracteres o string, como let mensaje = "Soy una morsa". También podemos asignar a una variable una función, como let saludo = function() { }. Lo que hay después del signo = es llamado Expresiones de función. Nos devuelve un valor especial (una función) que representa el código que contiene, de esta manera podemos llamar a ese código cuando queramos.
    • Declaración de función: Es muy cansado escribir algo como let saludo = function() { } cada vez que lo necesitemos, por eso podemos usar en su lugar: function saludo() { }. Esto se llama declaración de función. En lugar de especificar una variable a la izquierda, ponemos el nombre después de la palabra function. Estos estilos de declarar una función son casi siempre intercambiables.
    • Alzamiento de funciones o Hoisting: Normalmente, una variable solo puede ser usada después de declararla con let o const. Esto puede ser molesto cuando se trata de funciones porque puede que estas se llamen unas a otras, y es difícil rastrear qué función es utilizada por otras y cuales necesitan declararse primero. Por comodidad, cuando (¡y solo cuando!) usemos la sintaxis de declaración de función, el orden de las declaraciones no importa porque se “alzan”. Esta es una forma elegante de decir que conceptualmente, todas las funciones se trasladan automáticamente a la parte superior del alcance. Así que cuando llamamos a las funciones todas han sido ya declaradas.
    • this: Este es probablemente el concepto más incomprendido en JavaScript, this es como un argumento especial en una función. No hace falta pasárselo a la función nosotros mismos, JavaScript lo pasa por nosotros, dependiendo de como ejecutemos la función. Por ejemplo, ejecutarla usando la notación por punto . — como en helado.comer() — nos darán un valor this especial para lo que haya delante del punto . (en el ejemplo, helado). El valor de this dentro de la función depende de como la función es llamada, no de donde es definida. Métodos como .bind, .call, y .apply nos ayudan a tener mas control sobre el valor de this.
    • Funciones flecha: Las funciones flecha son similares a las expresiones de función. Se declaran así: let saludo = () => { }. Son muy concisas y normalmente van en una sola línea. Las funciones flecha son más limitadas que las funciones regulares — por ejemplo, no tienen la variable this. Cuando escribes this dentro de una función flecha, esta usa el this de la función regular mas cercana que contenga esta función. Esto es similar a lo que pasa si se utiliza un argumento o una variable que solo existe en una función anterior. Básicamente, esto significa que la gente usa funciones flecha cuando quieren “ver” el mismo this dentro de ellas como en el código que las rodea.
    • Enlazado de funciones: Normalmente, enlazar una función f a un this especifico y a sus argumentos significa crear una nueva función que llame a f con esos valores predefinidos. JavaScript tiene un método incorporado por defecto llamado .bind, pero también se puede hacer “a mano”. Enlazar era un método común de hacer que varias funciones “viesen” el mismo this que las funciones exteriores. Pero ahora esto se hace con funciones flecha, por lo que ya no se “enlazan” funciones con tanta frecuencia.
    • Pila de llamadas: Llamar a una función es como entrar a una habitación. Cada vez que llamamos a una función, las variables dentro de ella se inicializan nuevamente. Entonces, cada llamada a la función es como construir una nueva “habitación” con su código y entrar en ella. Las variables de nuestra función “viven” en esa habitación. Cuando volvemos de la función, esa “habitación” desaparece con todas sus variables. Podemos visualizar estas habitaciones como una pila vertical de habitaciones: una pila de llamadas. Cuando salimos de una función, volvemos a la función “debajo” de la pila de llamadas.
    • Recursión: Recursión significa que una función se llama a si misma desde su interior. Esto es útil cuando quieres repetir otra vez lo que acabas de hacer pero con argumentos diferentes. Por ejemplo, si estás escribiendo un buscador que rastree la web, la función encuentraLinks(url) podría primero recolectar todos los links de una pagina, y luego llamarse a si misma por cada link hasta que visite todas las paginas de esa web. El problema con recursión es que es fácil escribir una función que termine en un loop infinito porque se llama a sí misma continuamente. Si esto ocurre, JavaScript terminara la ejecución de la función con un error de “stack overflow o desbordamiento de pila de llamadas”. Se llama así porque hemos hecho muchas llamadas a la misma función en la pila de llamadas, literalmente, desbordándola.
    • Funciones de orden superior o High-order function: Una función de orden superior es una función que trabaja con otras funciones usándolas como argumentos o devolviéndolas como resultado de su ejecución. Esto puede parecer raro al principio, pero debemos recordar que las funciones son valores que se pueden usar por todo nuestro código — como hacemos con números, cadenas de caracteres y objetos. Este estilo no debe ser sobreutilizado, pero es muy expresivo si se usa con moderación.
    • Callback: Realmente Callback no es un término de JavaScript. Es más como un patrón. Es cuando pasamos una función como argumento de otra, esperando que la función que recibe la otra función como argumento la ejecute después. De esa manera esperamos un callback o llamado posterior. Por ejemplo, setTimeout recibe una función como callback y… la llama después de que pase un tiempo. No hay nada especial sobre las funciones callback. Son funciones normales, así que cuando decimos “callback” en realidad nos referimos a las expectativas que tenemos.
    • Clausura: Normalmente, cuando una función termina, todas sus variables “desaparecen”. Esto ocurre porque ya no se necesitan. ¿Pero qué pasa si declaras una función dentro de otra función? Entonces la función de dentro podría ser ejecutada más tarde y podría necesitar acceder/leer las variables de fuera, las de la función contenedora. ¡En la práctica, esto es muy útil! Pero para que esto funcione, las variables de la función contenedora necesitan “quedarse por ahí” en algún sitio. Así que en estos casos, JavaScript se encarga de “mantener esas variables vivas” en lugar de “olvidarse” de ellas como haría normalmente. Esto se llama “clausura”. Aunque las clausuras son normalmente un concepto bastante incomprendido de las características de JavaScript, probablemente las has utilizado muchas veces sin darte cuenta!

JavaScript está hecho de estos conceptos, y muchos más. En mi caso sentía mucha ansiedad en relación a mis conocimientos sobre JavaScript hasta que me formé un mapa mental fuerte sobre todo esto, así pues, me gustaría ayudar a las nuevas generaciones de programadores a superar estas dificultades mas fácilmente.

Si quieres unirte a mí para sumergirnos mas profundamente en todos estos conceptos, tengo algo que podría interesarte. Just JavaScript es mi mapa mental de como JavaScript funciona, y contará con las ilustraciones de la increíble Maggie Appleton. Al contrario que este post, va a un ritmo más lento para que pueda ser seguido con más detalle.

Just JavaScript esta dando sus primeros pasos y solo esta disponible por email sin una edición muy cuidada. Si esto te parece interesante, puedes darte de alta para recibir los borradores por email. Sería de mucha ayuda si me pudieses dar tu opinión. Muchas gracias!