05. Funciones - cejaramillof/javaScript GitHub Wiki

Funciones (functions)

Son fracciones de código re-utilizables. Recibe parámetros delimitados en paréntesis separados por comas (p1, p2) por referencia o valor, son opcionales (porque javascript es interpretado es decir intenta ejecutar el código sin importar los argumentos). Y pueden retornar un valor. El cuerpo de la función se delimita con llaves { }

// Función Declarativa, se le aplica Hoisting, podemos llamarla antes de inicializarla
function nombreFuncion(params) {
    // Cuerpo
    return "finish";// Valor retornado
}
// No es recomendable inicializarla así, porque pueden ser sobre escritas en el código
nombreFuncion = 'Hola';

Si una función no tiene nombre, es una función anónima. Una función se puede asignar a una variable.

// Expresión de Función, no se le aplica Hoisting, no podemos llamarla antes de inicializarla
// Opcionalmente podría llevar nombre, pero es más común que se hagan anónimas
const esMayorEdad = function (persona) {
    return persona.edad >= MAYORIA_EDAD
}

String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
}

Enviar función como parametro


class Desarrollador extends Persona {
    constructor(nombre, apellido, altura) {
        // llamar constructor clase padre (necesario para usar this)
        super(nombre, apellido, altura);
        /*
        this.nombre = nombre;
        this.apellido = apellido;
        this.altura = altura;
        */
    }
    
    saludar(fn) {
        var {nombre, apellido} = this;
        console.log(`Hola, me llamo ${nombre} y soy dev!`);            
        if (fn) {
            fn(nombre, apellido, true)
        }
    }
}
function responderSaludo(nombre, apellido, esEstudiante){
  console.log(`buenas tardes ${nombre}${apellido}`)
  if(esEstudiante){
    console.log(`Ah, no sabie que eres estudiante.`)
  }
}

carlos.saludar(responderSaludo);

Funciones Flecha (Arrow Function)

Se le quita la palabra function, y se agrega =>, y si solo recibe un parámetro, podemos obviar los paréntesis. Una característica especial de estas funciones es que no generan un nuevo contexto (this) entonces toman el de su padre.

const ES_MAYOR_EDAD = persona => { 
    return persona.edad >= MAYORIA_EDAD
}

Si solo retorna algo, podemos borrar el return y las { llaves }

const ES_MAYOR_EDAD = persona => persona.edad >= MAYORIA_EDAD

O también podemos agregarle parentesis cuando retornaremos un objeto o multiples lineas

const USER = persona => ({
    uid: 'ABC',
    username: persona.nombre
})

Las funciones normales tienen una propiedad de prototipo y estas funciones arrow no tienen esa propiedad de prototipo es (undefined)

Daño Colateral (Side Effect)

Ocurre cuando al ejecutar una función, ella modifica variables que no están definidas dentro de ella.

//Evitar Side Effect con Estructura de datos inmutable
function cumpleanos(persona) {
    // Retorna un objeto nuevo con la edad modificada 
    return {...persona, edad: persona.edad + 1}
    //(... Spread Operator) clona un objeto o array
}

Immediately Invoked Function Expression (IIFE)

Es una expresión funcional que se invoca inmediatamente. Su sintaxis es: (/* funcion */)();

(function () {
 
})();

Funciones Recursivas (Recursividad)

Aquella que se llama (o se ejecuta) a sí misma de forma controlada, hasta que sucede una condición base.

/*
13 /____4___

13 - 4 = 9      1
9 -  4 = 5      1
5 -  4 = 1      1
1 -  4 = -3     0
________________________
                3
*/
function divisionEntera(dividendo, divisor){
    if (dividendo < divisor){
        return 0;
    }
    return 1 + divisionEntera(dividendo - divisor, divisor);
}

let divicionEntera = (dividendo, divisor) => 
    (dividendo < divisor) ? 
        0 : 1 + divicionEntera(dividendo - divisor, divisor);

Memoización (Memoization)

Consiste en ir almacenando el resultado invariable de una función para que no sea necesario volver a ejecutar todas las instrucciones de nuevo, cuando se vuelva a llamar con los mismos parámetros. Es similar a usar memoria cache.


function factorial(n) {
    if (n < 0) {
        return 'Entrada no válida, solo numeros positivos';
    }

    if (!this.cache) {
        this.cache = {}                
    }
    
    if (this.cache[n]) {
        return this.cache[n]
    }

    if (n === 1 || n === 0) {
        this.cache[n] = 1
        return this.cache[n]
    }

    this.cache[n] = n * factorial(n - 1)

    return this.cache[n] 
}

Closures

Un clousure es la combinación de una función y el ambito o léxico en la cual ha sido declarada dicha función.

Un closure, básicamente, es una función que recuerda el estado de las variables al momento de ser invocada, y conserva este estado a través de reiteradas ejecuciones. Un aspecto fundamental de los closures es que son funciones que retornan otras funciones u objetos con funciones que mantienen las variables que fueron declaras fuera de su scope.

Los closures nos sirven para tener algo parecido a variables privadas, característica que no tiene JavaScript por default. Es decir encapsulan variables que no pueden ser modificadas directamente por otros objetos, sólo por funciones pertenecientes al mismo.

El problema es que color está disponible globalmente

let color = 'green';
function printColor() {
  console.log(color);
}
printColor();

Para solucionarlo, creamos una función:

// IIFE
(function() {
  // Es accesible únicamente por las funciones retornadas al scope global.
  let color = 'green';
  // clousure
  function printColor() {
    console.log(color);
  }
  printColor();
})();

Es la combinación del scope de una función y el scope donde fue definida, donde el scope de la función es la función IIFE la función principal, y adentro la función que fue definida dentro de ese scope que tiene acceso a lo que estaba afuera.

function makeColorPrinter(params) {
  let colorMessage = `The color is ${params}`;
  return function() {
    console.log(colorMessage);
  };
}

let greenColorPrinter = makeColorPrinter("green");
console.log(greenColorPrinter());
const counter = {
  count: 3
}
// count esta en el scope Global y su valor puede ser modificado fácilmente
counter.count = 99;
console.log(counter.count);

// Closures - creamos un function scope
function makeCounter(n) {
  // count no existe en window, solo pertenece a la función
  let count = n;
  return {
    increase: function () { count += 1; },
    decrease: function () { count -= 1},
    getCount: function () { return count; },
    setCount: (newCount) => { count = newCount },
  }
}
let counter = makeCounter(7);
console.log('This count is:', counter.getCount());
console.log('This count is:', counter.increase());
console.log('This count is:', counter.decrease());
// No podemos cambiar el valor de count porque no está en nuestro alcance.
counter.count = 99; // ERROR FATAL
function inicia() {
  var nombre = "Mozilla"; // Variable local creada por la función inicia
  function muestraNombre() { // Es una función interna (un closure)
    // dentro de esta función usamos una variable declarada en la función padre
    alert(nombre);
  }
  muestraNombre();
}
inicia(); 
function crearSaludo(finalDefrase) {
  return function (nombre) {
    console.log(`Hola ${nombre}${finalDefrase}`)
  }
}

//crea los saludos con los finales de frase
const saludoArgentino = crearSaludo('che')
const saludoMexicano = crearSaludo('way')
const saludoColombiano = crearSaludo('parcero')

saludoArgentino('Omar') // Hola Omar che
saludoMexicano('Omar') // Hola Omar way
saludoColombiano('Omar') // Hola Omar parcero

Closures y Loops

const anotherFunction = () => {
  for(var i = 0; i < 10; i++) {
    setTimeout(() => {
      console.log(i)
    }, 0)
  }
}
anotherFunction(); // will print 10, 10 times.

// Tenemos que tener muy en cuenta el alcance de las variables en un closure, para no ocasionar estos bugs
const anotherFunction2 = () => {
  for(let i = 0; i < 10; i++) {
    setTimeout(() => {
      console.log(i)
    }, 0)
  }
}
anotherFunction2(); // will print the numbers 1 to 10

Curry (Currying)

Poder llamar una función con menos parámetros de los que espera, esta devuelve una función que espera los parámetros restantes y retorna el resultado.

//ES2015
const divisible = mod => num => num % mod;

//ES5
var divisible = function (mod) {
  return function (num) {
    return num % mod;
  }
}

// Pasar los argumentos ejecutando la funciones
divisible(10)(2)

// Pasar un argumento y recibir una función que recuerde este argumento
const divisibleEn3 = divisible(3);
divisibleEn3(10)
function caminar(metros, direccion) {
  console.log(`${this.name} camina ${metros} metros hacia ${direccion}`);
}
const carlos = {
  name: "Carlos",
  lastName: "Jaramillo"
}

const carlosCamina = caminar.bind(carlos, 1000);
carlosCamina('SurOeste');

This

this se refiere a un objeto, ese objeto es el que actualmente está ejecutando un pedazo de código. No se puede asignar un valor a this directamente y este depende de en que scope nos encontramos:

  • this en Global Scope o Function Scope, hace referencia al objeto window
    • A excepción de cuando estamos en strict mode que nos regresará undefined.
  • this en una función que está contenida en un objeto hace referencia a ese objeto.
  • Cuando llamamos a this desde una "clase", hace referencia a la instancia generada por el constructor.
const person = {
  name: "Carlos",
  saludar: function() {
    console.log(`Hola soy ${this.name}`);
  }
};
person.saludar(); // Hola soy Carlos
const accion = person.saludar;
accion(); // Hola soy 
// Porque el contexto no es person, si no accion

Cambiar de contexto al llamar función

El contexto (o alcance) de una función es por lo general, window. Así que en ciertos casos, cuando intentamos referirnos a this en alguna parte del código, es posible que tengamos un comportamiento inesperado, porque el contexto quizás no sea el que esperamos.

Existen al menos tres maneras de cambiar el contexto de una función:

  • Método .bind(), enviamos la referencia de la función sin ejecutarla, pasando el contexto como parámetro. Cualquier parámetro adicional se coloca luego seguido de comas. No ejecuta la función, sólo regresa el otra función con el nuevo this integrado.
  • Método .call(), ejecutamos inmediatamente la función con el contexto indicado. Cualquier parámetro adicional se coloca luego seguido de comas. Usando el método .apply(), es similar a .call() pero los parámetros adicionales se pasan como un arreglo de valores
  • Pasar el contexto del this requerido a una variable local y usar ésta dentro del nuevo contexto. Usualmente llamada self en reemplazo let self = this
const carlos = {
    nombre : 'Carlos',
    apellido : 'Jaramillo',
    edad : 23
}

function saludar(saludo = 'Hola'){
    console.log(`${saludo}, mi nombre es ${this.nombre}`);
}

const saludarACarlos = saludar.bind(carlos);
setTimeout(saludar.bind(carlos, 'Hola che'), 1000);
saludar.call(carlos, 'Hola che');
saludar.apply(carlos, ['Hola che']);

Es importante tener presente que siempre que ejecutamos una función asíncrona el .this cambia, y es muy importante atarlo a nuestra clase, objeto o función.