Fundamentos

Variables

Estados de una variable

Declarar

var edad;

Inicializar

edad = 30;

Var

Era la forma en que se declaraban las variables hasta ECMAScript 5. El ámbito de una variable declarada con la palabra reservada var es su contexto de ejecución en curso, que puede ser la función que la contiene o, para las variables declaradas afuera de cualquier función, un ámbito global, se convertirá en una propiedad del objeto global "window" en el navegador o del objeto "global" en NodeJs. Si re-declaras una variable Javascript, esta no perderá su valor.

var animal = 'dog';

Const

Sirve para declarar variables que nunca van a ser modificadas, características:

  • No se puede reinicializar

  • No se pude reasignar

  • Es inmutable, pero si contienen un arreglo de objetos, por ejemplo, se se podria mutar alguno de ellos, aunue sea una constante.

const animalObj = { name: 'fido', age: '60' };
animalObj.name = 'panter';

Let

Son variables que pueden ser modificadas, se pueden cambiar sus valores, mas no reinicializar.

let animal = 'dog';
animal = 'cat';

Funciones

Las funciones son las operaciones que va a ejecutar el interprete de codigo.

Declarativas (function declaration / function statement)

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

De expresión (function expression)

const suma = function(a, b){
    return a + b;
}

Diferencias

A las funciones declarativas se les aplica hoisting, y a la expresión de función, no. Ya que el hoisting solo se aplica en las palabras reservadas var y function.

Lo que quiere decir que con las funciones declarativas, podemos mandar llamar la función antes de que ésta sea declarada, y con la expresión de función, no, tendríamos que declararla primero, y después mandarla llamar.

Scope

El ámbito o alcance que una variable tendrá en el código.

Global

Se dice que una variable está en el sope global cuando está declarada fuera de una función o de un bloque. También son conocidas como variables globales. Puedes acceder a este tipo de variables desde cualquier parte de tu código, ya sea dentro o fuera de una función.

const soyEstudiante = true;

function platzi() {
    console.log(soyEstudiante);
}

platzi(); //true
console.log(soyEstudiante); //true

A pesar de que JavaScript nos permite declarar una variable como global, no es una buena práctica. Una de las razones es porque tenemos la posibilidad de declarar dos variables globales en distintas partes del código con el mismo nombre sin notarlo.

En JavaScript, si asignas un valor a una variable sin declararla previamente con var, let o const, fuera de una funcion o bloque, esa variable se crea automáticamente en el ámbito global (window).

valor = 1;

Local

Función

Se refiere al scope que abarca únicamente el cuerpo de una función o cualquier bloque de código encerrado entre llaves {}. Las variables y funciones declaradas dentro de una función o bloque de código solo son accesibles dentro de ese ámbito local.

Un ejemplo de esto es cuando una variable está declarada dentro de un bloque o una función. Si tratas de acceder a esta variable fuera de esta función o bloque, tendrás un error que dirá que la variable no está definida.

function platzi() {
    const soyEstudiante = true;
    console.log(soyEstudiante);
}

platzi(); // true
console.log(soyEstudiante); // ReferenceError: soyEstudiante is not defined

Bloque

Además, a partir de la versión ES6 de JavaScript, también existe el ámbito de bloque, que se refiere a cualquier bloque de código encerrado entre llaves {} (como un if, un for, un while, etc.). Las variables declaradas con let y const tienen ámbito de bloque, lo que significa que solo son accesibles dentro del bloque donde se declararon.

Módulo

También existe el ámbito de módulo, que se refiere al alcance de variables, funciones y clases en un módulo de JavaScript. Un módulo es un archivo de JavaScript que se importa y se exporta para su uso en otros archivos de JavaScript.

En el ámbito de módulo, las variables y funciones se definen como miembros del objeto module o exports, y no están disponibles en el ámbito global.

Lexical Scope / Ambito lexico

El intérprete de JavaScript funciona desde el ámbito de ejecución actual y funciona hasta encontrar la variable en cuestión. Si la variable no se encuentra en ningún ámbito, se genera una excepción.

Este tipo de búsqueda se llama ámbito léxico. El alcance de una variable se define por su ubicación dentro del código fuente, y las funciones anidadas tienen acceso a las variables declaradas en su alcance externo. No importa de dónde se llame una función, o incluso cómo se llama, su alcance léxico depende solo de dónde se declaró la función.

Entonces se podria decir que el lexical scope es de adentro hacia afuera. Es decir que JS siempre busca las variables en el bloque más interno desde donde haya sido llamada. Por ejemplo:

const scope = "I'm global";
const func1 = () => {
  const scope = "I'm local 1";
  const func2 = () => {
    const scope = "I'm local 2";
    const func3 = () => {
      const scope = "I'm local 3";
      console.log(scope);
    }
    func3();
  }
  func2();
}
func1();

En el código anterior la salida será

I'm local 3

Debido a que el console.log(scope) busca la variable dentro de func3() (desde donde fué llamado) y al encontrar la variable scope = “I’m local 3” entonces la imprime. Pero si eliminamos esa linea y dejamos esto:

const scope = "I'm global";
const func1 = () => {
  const scope = "I'm local 1";
  const func2 = () => {
    const scope = "I'm local 2";
    const func3 = () => {
      console.log(scope);
    }
    func3();
  }
  func2();
}
func1();

La salida es:

I'm local 2

Si observamos dentro de fun3() ya no existe ninguna definición de la variable scope, por lo que JS buscará por fuera de este bloque pasando a al bloque func2(), en donde encuentra const scope = “I’m local 2”. Y asi sucesivamente podriamos ir eliminando definiciones de scope de adentro hacia afuera:

const scope = "I'm global";
const func1 = () => {
  const scope = "I'm local 1";
  const func2 = () => {
    const func3 = () => {
      console.log(scope);
    }
    func3();
  }
  func2();
}
func1();

Cuya salida es:

I'm local 1

Finalmente tenemos:

const scope = "I'm global";
const func1 = () => {
  const func2 = () => {
    const func3 = () => {
      console.log(scope);
    }
    func3();
  }
  func2();
}
func1();

Cuya salida es:

I'm global

Pero este coportamiento siempre es exclusivamente de adentro hacia afuera, y por tanto si intentamos algo como esto:

const func1 = () => {
  const func2 = () => {
    const func3 = () => {
      const scope = "I'm local 3";
    }
    console.log(scope);
    func3();
  }
  func2();
}
func1();

JS devuelve un ReferenceError ya que console.log(scope) fue llamado desde func2(); por fuera de func3() en donde se encuentra definido const scope = “I’m local 3”. Por lo que JS no puede encontrarlo y devuelve el error.

Hoisting

El Hoisting es un proceso del compilador de JavaScript, que consiste en que la declaración de las variables con las palabras reservadas tipo var y function son llevadas al inicio del ámbito en donde fueron declaradas (scope), sin importar su posición, para su procesamiento, sin embargo, la inicialización de las variables no es llevada al inicio del código para su compilación, sino solo su declaración, lo cual suele dar espacio a errores cuando se declara una variable sin inicializarla y se procesa en el código antes de haber llegado a su inicialización, lo cual nos suele dar una variable con valor undefined, ya que la variable sí fue almacenada en memoria, pero no se le asigno un valor hasta después de su ejecución.

Ejemplo:

saludo();
function saludo() {
    console.log("Hola " + nombre);
}
var nombre = "John"; // Global scope
//> Hola undefined
// o
saludo();
function saludo() {
    console.log("Hola " + nombre);    
    var nombre = "John"; // Local scope
}
//> Hola undefined

El output de este código seria el siguiente:

Hola undefined

Es por eso que se tiene como buena practica declarar e inicializar tanto variables como funciones al inicio de nuestro programa, sin importar donde sean utilizadas, pues de esta manera se evita usarlas antes de ser inicializadas.

Coerción

Coerción es la forma en la que podemos cambiar un tipo de valor a otro, existen dos tipos de coerción.

Coerción implícita

Es cuando el lenguaje nos ayuda a cambiar el tipo de valor.

var a = 4 + "7"; //Convierte a 4 en un string y lo concatena con el "7", por esto regresa un string de valor "47"

var b = 4 * "7"; //Convierte al "7" en un número y realiza la operación, por esto devuelve 28

Coerción explicita

Es cuando obligamos a que cambie el tipo de valor.

var c = String(a); //Aquí obligamos a la variable a convertirse en string (coerción explícita)
console.log(c);

var d = Number(c); //Aquí obligamos a la variable a convertirse en número (coerción explícita)
console.log(d);

Nullish coalescing operator (??) vs logical OR operator (||)

La gran diferencia entre estos dos es que el logical OR operator (||) toma como valores falsos a 0, NaN, strings vacíos ("", '', ``), false, a demás de null y undefined, mientras que nullish coalescing operator (??) toma como valores falso solamente a null y undefined.

Truthy or Falsy?

Usamos la función de JS que es Boolean() dentro del paréntesis ponemos el valor y nos dice si el mismo el False o True.

Truthy

  • Boolean(1) —> true //cualquier numero que no sea igual a cero es true

  • Boolean(“a”) —> true

  • Boolean(" ") —> true // siendo un espacio el valor es true

  • Boolean([]) —> true // un array nos da un true

  • Boolean({}) —> true // un objeto nos da el valor de true

  • Boolean(function() {}) —> true //una funcion tambien es true

  • Boolean(true) —> true

Falsy

  • Boolean() —> sin ningun valor es false

  • Boolean(0) —> false

  • Boolean(null) —> false

  • Boolean(NaN) —> false // NaN es Not and Number

  • Boolean(Undefined) —> false

  • Boolean(false) —> false

  • Boolean("") —> false

Parámetros restantes (Spread Operatos)

Puede ser útil para una función aceptar cualquier cantidad de argumentos. Por ejemplo, Math.max calcula el máximo de todos los argumentos que le son dados.

Para escribir tal función, pones tres puntos antes del ultimo parámetro de la función, asi:

function maximo(...numeros) {
  let resultado = -Infinity;
  for (let numero of numeros) {
    if (numero > resultado) resultado = numero;
  }
  return resultado;
}
console.log(maximo(4, 1, 9, -2));
// → 9

Cuando se llame a una función como esa, el parámetro restante está vinculado a un array que contiene todos los argumentos adicionales. Si hay otros parámetros antes que él, sus valores no seran parte de ese array. Cuando, tal como en maximo, sea el único parámetro, contendrá todos los argumentos.

Puedes usar una notación de tres-puntos similar para llamar una función con un array de argumentos.

let numeros = [5, 1, 7];
console.log(max(...numeros));
// → 7

Esto “extiende” al array en la llamada de la función, pasando sus elementos como argumentos separados. Es posible incluir un array de esa manera, junto con otros argumentos, como en max(9, ...numeros, 2).

La notación de corchetes para crear arrays permite al operador de tres-puntos extender otro array en el nuevo array:

Square bracket array notation similarly allows the triple-dot operator to spread another array into the new array:

let palabras = ["nunca", "entenderas"];
console.log(["tu", ...palabras, "completamente"]);
// → ["tu", "nunca", "entenderas", "completamente"]

Shallow Copy

Cuando utilicé spread {...} para copiar un objeto, solo está creando una copia superficial. Si la matriz está anidada o es multidimensional, no funcionará. Aquí un ejemplo:

const nestedObject = {
  flag: '🇨🇦',
  country: {
    city: 'vancouver',
  },
};

// Clonemos nuestro objeto usando spread:

const shallowClone = { ...nestedObject };

// Changed our cloned object
shallowClone.flag = '🇹🇼';
shallowClone.country.city = 'taipei';

// Así que cambiamos nuestro objeto clonado cambiando la ciudad. Veamos la salida.

console.log(shallowClone);
// {country: '🇹🇼', {city: 'taipei'}}

console.log(nestedObject);
// {country: '🇨🇦', {city: 'taipei'}} <-- 😱

// Una copia superficial significa que se copia el primer nivel, se hace referencia a los niveles más profundos.

Deep Copy

En JavaScript los objetos son valores de referencia, entonces no se puede simplemente clonar un objeto usando el =. Aquí hay 3 formas de clonar un objeto.

const food = { beef: "🥩", bacon: "🥓" };

// "Spread"
const foodSpread = { ...food };

// "Object.assign"
const foodAssign = Object.assign({}, food);
const foodAssign2 = Object.assign(food, { beef: "🌽" });

// "JSON"
const foodJson = JSON.parse(JSON.stringify(food));
console.log(food, foodAssign, foodJson);

Mencionar que esta es una forma rápida y sucia de clonar un objeto en profundidad. Para una solución más robusta, recomendaría usar algo como lodash.

structuredClone

Es un método en JavaScript que permite crear una copia profunda (deep copy) de un objeto. Esto significa que se clona tanto el objeto como todos los objetos anidados dentro de él, sin mantener referencias a los objetos originales. Este método es útil para duplicar estructuras de datos complejas sin preocuparse por las referencias compartidas que podrían causar efectos secundarios no deseados.

Sintaxis

const copia = structuredClone(objetoOriginal);

Ejemplo

const original = {
  nombre: "John",
  edad: 30,
  direccion: {
    ciudad: "Madrid",
    pais: "España"
  }
};

const copia = structuredClone(original);

console.log(copia);
// Salida: { nombre: 'John', edad: 30, direccion: { ciudad: 'Madrid', pais: 'España' } }

// Modificar la copia no afecta al original
copia.direccion.ciudad = "Barcelona";
console.log(original.direccion.ciudad); // Salida: Madrid
console.log(copia.direccion.ciudad); // Salida: Barcelona

Ventajas

  • Copia profunda: A diferencia de métodos como Object.assign o el operador de propagación (...), structuredClone realiza una copia profunda.

  • Simplicidad: Es sencillo de usar y no requiere librerías adicionales ni soluciones complejas para copiar objetos anidados.

Consideraciones

  • Compatibilidad: structuredClone es una funcionalidad relativamente nueva y puede no estar disponible en todos los entornos. Es importante verificar la compatibilidad con los navegadores o entornos de ejecución que se estén utilizando.

Lodash DeepClone vs JSON

  • JSON.stringify/parse solo funciona con números, cadenas y objetos literales sin propiedades de función, Date o Symbol.

  • deepClone funciona con todos los tipos, funciones, fechas y símbolos se copian por referencia.

Más detalles https://www.samanthaming.com/tidbits/70-3-ways-to-clone-objects/

Funciones de orden superior

Las funciones que operan en otras funciones, ya sea tomándolas como argumentos o retornándolas, se denominan funciones de orden superior. Como ya hemos visto que las funciones son valores regulares, no existe nada particularmente notable sobre el hecho de que tales funciones existen. El término proviene de las matemáticas, donde la distinción entre funciones y otros valores se toma más en serio.

Las funciones de orden superior nos permiten abstraer sobre acciones, no solo sobre valores. Estas vienen en varias formas. Por ejemplo, puedes tener funciones que crean nuevas funciones.

function mayorQue(n) {
  return m => m > n;
}
let mayorQue10 = mayorQue(10);
console.log(mayorQue10(11));
// → true

Y puedes tener funciones que cambien otras funciones.

function ruidosa(funcion) {
  return (...argumentos) => {
    console.log("llamando con", argumentos);
    let resultado = funcion(...argumentos);
    console.log("llamada con", argumentos, ", retorno", resultado);
    return resultado;
  };
}
ruidosa(Math.min)(3, 2, 1);
// → llamando con [3, 2, 1]
// → llamada con [3, 2, 1] , retorno 1

Incluso puedes escribir funciones que proporcionen nuevos tipos de flujo de control.

function aMenosQue(prueba, entonces) {
  if (!prueba) entonces();
}

repetir(3, n => {
  aMenosQue(n % 2 == 1, () => {
    console.log(n, "es par");
  });
});
// → 0 es par
// → 2 es par

Hay un método de array incorporado, forEach que proporciona algo como un ciclo for/of como una función de orden superior.

["A", "B"].forEach(letra => console.log(letra));
// → A
// → B

Las funciones de orden superior comienzan a brillar cuando necesitas componer operaciones. Si por ejemplo estas computando arrays con ciertas operaciones dentro, como condiciones por ejemplo, es más difícil ver qué se está calculando y en sus resultados intermedios si utilizamos solo ciclos. En otras palabras con funciones de orden superior es mas legible el código.

Ver programación funcional

Last updated