-
Notifications
You must be signed in to change notification settings - Fork 1
05. Funciones
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();
}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);Colecciones (Arreglos), también podemos trabajar con "colecciónes" con iteradores y generadores. Es relativo, porque los iteradores y generadores pueden iterar sobre un rango de datos sin la necesidad de una colección. La clara ventaja es rendimiento, porque cuando usamos un arreglo en JS, es necesario almacenar el arreglo completo en la memoria virtual (es trivial con arreglos pequeños), si quisieramos iterar de 0 a infinito sería imposible con arreglos porque la memoria virtual tiene un limite. Aqui entran los iteradores y generadores con los cuales podemos desarrollar nuestros propios mecanismos de iteración sin tener que alojar toda la colección en la memoria virtual, trabajando con un dato a la vez. Adicionalmente estas nuevas estructuras nos permiten implementar el concepto de iteración sobre objetos personalizados.
De acuerdo al portal de dev de mozilla, un Iterador es cualquier objeto que implementa el Iterator protocol, es decir cualquier objeto que implemente un metodo next, que retorne un objeto con una propiedad value y una propiedad done, implementa el protocolo y por lo tanto es un iterador
Caracteristicas:
- Aun que la intención es iterar una colección de valores, los valores se producen 1 a la vez. No hay arreglos ni estructuras que almacenen todos los datos, lo que beneficia el espacio en memoria. Cada valor que se itera se produce cuando llamamos next
- La interacción es
lazy, cuando recorremos con un ciclo un arreglo, todos los elementos se imprimen tan pronto como es posible para la PC, con un iterador podríamos llamar el siguiente elemento cuando queramos o no hacerlo. - Los iteradores no tienen forma de reiniciarlos, sólo se recorren una vez. Cada llamada despues de que el iterador haya terminado de recorrerse debe retornar el valor final del iterador, y la propiedad
done: true - Podemos recorrer un iterador con un ciclo
let iterador = {}; // empty object// this object implements iterator protocol, so right now is an iterator
let iterador = {
next(){
return {
value: null,
done: true,
}
}
}; // iterator working
let iterador = {
currentValue: 1,
next(){
let result = { value: null, done: false};
if (this.currentValue > 0 && this.currentValue <= 5) {
result = { value: this.currentValue, done: false };
this.currentValue += 1;
} else {
result = { done: true };
}
return result;
}
};
console.log(iterator.next()); // { done: false, value: 1 }
console.log(iterator.next()); // { done: false, value: 2 }
let item = iterador.next();
while(!item.done) {
console.log(item.value);
item = iterador.next();
}Son usados por las librerías co, redux-sagas, y más.
Es muy similar a un iterador, tiene algunos beneficios como la sintaxis, porque en un generador no tienes que hacerte cargo del estado del objeto. Algunos los definen como funciones, que pueden ser detenidas en su ejecución para luego ser reanudadas en el punto en el que se quedaron.
function* counter(){
console.log('estoy aqui');
yield 1;
console.log('ahora estoy aqui');
yield 2;
}
let generator = counter();
console.log(generator.next()); // print first console log, and { done: false, value: 1 }
console.log(generator.next()); // print second console log, and { done: false, value: 2 }
console.log(generator.next()); // { done: true, value: undefined }// the same example as a iterator, but with generators
function* counter(){
for(var i = 1; i <= 5; i++) {
yield i;
}
}
let generator = counter();
console.log(generator.next()); // { done: false, value: 1 }
console.log(generator.next()); // { done: false, value: 2 }
console.log(generator.next()); // { done: false, value: 3 }
console.log(generator.next()); // { done: false, value: 4 }
console.log(generator.next()); // { done: false, value: 5 }
console.log(generator.next()); // { done: true, value: undefined }Yield es muy similar a return, porque yield produce valores para un generador. Aunque no mandamos a llamar return desde la función generador el lo hace de manera implicita.
- Si mandamos a llamar return con un valor en una función generadora, será igual a yield pero poniendo el done en true. Es decir, sólo puede ser llamado 1 vez.
function* retornador(){
return 3;
// if we add yield here never will be returned;
yield 5;
}
let g = retornador();
console.log(g.next()); // { done: true, value: 3 }
console.log(g.next()); // { done: true, value: undefined }Son funciones especiales, pueden pausar su ejecución y luego volver al punto donde se quedaron recordando su scope.
- Los generadores regresan una función.
- Empiezan suspendidos y se tiene que llamar next para que ejecuten.
- Regresan un value y un boolean done que define si ya terminaron.
- yield es la instrucción que regresa un valor cada vez que llamamos a next y detiene la ejecución del generador.
function* simpleGenerator() {
console.log("GENERATOR START");
yield 1;
yield 2;
yield 3;
console.log("GENERATOR END");
}
const gen = simpleGenerator();
gen.next();
// GENERATOR START
// {value: 1, done: false}
gen.next();
// {value: 2, done: false}
gen.next();
// {value: 3, done: false}
gen.next();
// GENERATOR ENDSe prestan para crear funciones eficientes en memoria, ejemplo la secuencia fibonacci, una función que imprime la secuencia, que lo que hace es sumar los dos número anteriores para generar uno nuevo.
function* fibonacci() {
let a = 1, b = 1;
while (true) {
const nextNumber = a + b;
a = b;
b = nextNumber;
yield nextNumber;
}
}Yield, se manda a llamar junto a una expresión que produce un resultado. Este resultado eventualmente se asigna a la propiedad value que retorna yield.
A Yield también podemos enviarle una función generadora y delegar la continuidad de la ejecución del código a otro generador. Esto se llama delegación de generadores porque un generador delega a otro generador la continuidad. Esto nos permite encadenar cuantos generadores queramos.
function* counter(){
for(var i = 1; i <= 5; i++) {
yield i;
}
}
function* retornador() {
yield* counter();
console.log('regresé);
yield 3;
}
let g = retornador();
console.log(g.next()); // { done: false, value: 1};
console.log(g.next()); // { done: false, value: 2};
console.log(g.next()); // { done: false, value: 3};
console.log(g.next()); // { done: false, value: 4};
console.log(g.next()); // { done: false, value: 5};
console.log(g.next()); // log 'Regresé' and { done: true, value: undefined }; // si no hay más yield
console.log(g.next()); // log 'Regresé' and { done: false, value: 3 }; // cuando hay otro yieldLos iterables (arreglos, cadenas, etc) nos permiten con el protocolo iterable definir el comportamiento de uno objetos cuando lo pasamos por un ciclo for of.
let counter(){
for(var i = 1; i <= 5; i++){
yield i;
}
}
let numeros = [2, 5, 10];
for (numero of numeros) { console.log(numero); } // 2, 5, 10
let generator = counter();
for (numero of generator) { console.log(numero); } // 1, 2, 3, 4, 5Para que un objeto sea iterable, necesita implementar un metodo llamado @@iterator, este metodo está identificado en los objetos por un simbolo, y no por una cadena. Lo encontramos en la constante Symbol.iterator del lenguage. Este es uno de los well-known symbols.
Lo importante de este método, es que debe retornar un objeto iterator (que tenga un método next, y que retorne un objeto con las props value y done).
// iterator working
let contador = {
[Symbol.iterator]() { // implements of @@iterator and return an iterator
return {
currentValue: 1,
next() {
let result = { value: null, done: false};
if (this.currentValue > 0 && this.currentValue <= 5) {
result = { value: this.currentValue, done: false };
this.currentValue += 1;
} else {
result = { done: true };
}
return result;
}
}
}
}
for (numero of contador) { console.log(numero) }; // 1, 2, 3, 4, 5Se 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_EDADO también podemos agregarle parentesis cuando retornaremos un objeto o multiples lineas
const USER = persona => ({
uid: 'ABC',
username: persona.nombre
})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
}Es una expresión funcional que se invoca inmediatamente. Su sintaxis es: (/* funcion */)();
(function () {
})();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);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]
}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 FATALfunction 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 parceroClosures 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 10Poder 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 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 accionEl 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.