Introducción a Funciones

JavaScript República Dominicana

Preparando el piso

Antes de empezar, primero hay que entender que son:

  • Declaraciones (statements)
  • Expresiones (expressions)

Las declaraciones hacen cosas. Un programa es una secuencia de declaraciones. Ej:

var foo;
var bar;

Las expresiones retornan cosas. Generalmente están del lado derecho de una asignación, o en los argumentos de una función. Ej:

2 + 2;
var foo = 'bar';
$('a[href="/"]').html();

Algunos afirman, otros expresan

Las formas if-else ilustran la distinción entre declaraciones y expresiones

// Declaración.
var x;
if (y >= 0) {
    x = y;
} else {
    x = -y;
}

// Expresión.
var x = y >= 0 ? y : -y;

Empezando a funcionar: FS vs. FE

Function Statements (FS)

function sum(x, y) {
    return x + y;
}
sum(13, 10); // 23

Function Expression (FE)

var sum = function (x, y) {
    return x + y;
};
sum(13, 10); // 23

Dos maneras de conseguir el mismo resultado. Solo que...

FSs: Hoisting

Las FSs son "elevadas" al inicio del bloque de código. Este comportamiento se conoce como hoisting.

// Used...
sum(21, 28); // 49

// Before being declared.
function sum(x, y) {
    return x + y;
}

sum(20, 8); // ReferenceError!

var sum = function (x, y) {
    return x + y;
};

Continuación

Según las especificaciones del lenguaje, esto es inválido:

if (x == 2) {
    a = sum(4,5); // 9

    function sum(param1, param2) {
        return param1 + param2;
    };
}

El hoisting ocurre antes de procesar el resultado del if, y los navegadores te dejan hacer el hoisting de todas maneras. Pero como la especificación del lenguaje no dice qué hacer en este caso, cada browser hace algo diferente, por lo que este es uno de esos casos extremos que se deben evitar. — Douglas Crockford.

First Class Citizens

Funciones como objectos.

Las funciones son en realidad instancias del prototipo Function.

function goToBedAt(time) {
    // ...
}
typeof fn; // "function"

var fn = function (x) { return x; };
typeof fn; // "function"

Pueden ser creadas usando el constructor 'Function'.

var call = new Function('who', 'when', "console.log('Calling ', who, ' at ', when);"); // NOT RECOMMENDED.

Funciones: Propiedades & Métodos

Propiedades

  • name
  • length
  • caller
  • constructor

Métodos

  • call
  • bind
  • apply

Funciones: Unidades de ejecución

Podemos pasar una función como argumento a otras funciones, de forma tal que se pueda ejecutar en esas otras funciones.

function suma(sumando1, sumando2) {
    return sumando1 + sumando2;
}

var resta = function(minuendo, sustraendo) {
    return minuendo - sustraendo;
};

var operacion = function(num1, num2, fn) {
    return fn(num1, num2)
};

operacion(2, 2, suma); // 4
operacion(10, 8, resta); // 2
operacion(56, 7, function(dividendo, divisor) {
    return dividendo / divisor;
});

Callbacks: No nos llames, nosotros te llamaremos

Razón principal para usar funciones como unidades de ejecución es para el manejo de código asincrónico, i.e: animaciones, AJAX, eventos, etc.

$("#box").animate({"margin-top" : "20px"}, function() {
    $(this).css("background-color", "blue");
});app.fetchOrders(function (err, orders) {
    if (err) throw err;

    //...
});
document.getElementById("box").addEventListener("click", (function(e) {
    alert("Hola mundo!");
});

Callbacks: Entrando al infierno

Las cosas se pueden poner salvaje cuando se anidan callbacks. A esta situación se le llama callback hell.

var p_client = new Db('integration_tests_20', new Server("127.0.0.1", 27017, {}), {'pk':CustomPKFactory});
p_client.open(function(err, p_client) {
    p_client.dropDatabase(function(err, done) {
        p_client.createCollection('test_custom_key', function(err, collection) {
            collection.insert({'a':1}, function(err, docs) {
                collection.find({'_id':new ObjectID("aaaaaaaaaaaa")}, function(err, cursor) {
                    cursor.toArray(function(err, items) {
                        test.assertEquals(1, items.length);
                        // Let's close the db
                        p_client.close();
                    });
                });
            });
        });
    });
});

Se puede mitigar

Usando callbacks nombrados y closures*

 function onFind(client) {
     return function (err, cursor) {
         cursor.toArray(function (err, items) {
             test.assertEquals(1, items.length);
         });

         // Close db.
         client.close();
     };
 }

 function onInsert(client, collection) {
     return function (err, doc) {
         collection.find({_id: new ObjectID('abcdefgh')}, onFind(client));
     };
 }

 function onCreateCollection(client) {
     return function (err, collection) {
         collection.insert({a: 1}, onInsert(client, collection));
     };
 }

 function onDropDatabase(client) {
     return function (err) {
         client.createCollection('test_custom_key', onCreateCollection(client));
     };
 }

 var p_client = new Db(/* ... */);

 p_client.open(function (err, client) {
     client.dropDatabase(onDropDatabase(client));
 });

Más información sobre closures adelante.

Haciendo promesas

 var p_client = new Db(/* ... */);

 p_client
     .open()
     .then(function (client) {
         client.dropDatabase();
     })
     .then(function (client) {
         client.createCollection('test_custom_key');
     })
     .then(function (collection) {
         collection.insert({a: 1});
     })
     .then(function (collection) {
         collection.findById(new ObjectID('asdfjkl;'));
     })
     .then(function (item) {
         test.assertEquals(item._id, new ObjectID('asdfjkl;'));
     })
     .catch(function (err) {
         throw err;
     })
     .done(function () {
         // Close db.
         p_client.close();
     });

this: Esto es un misterio

Si la función es un método de un objeto particular, "this" se refiere a dicho objeto.

var person1 = {
    firstName : 'Alan',
    lastName : 'Brito',
    fullName: function() {
        return this.firstName + ' ' + this.lastName
    }
};

alert(person1.fullName()); // 'Alan Brito'

arguments: Argumentemos

Las funciones pueden tomar un número arbitrario de argumentos. Cada argumento es asignado a su parámetro correspondiente. El resto estará disponible a través de "arguments".

function f() {
    return arguments;
}

var args = f('a', 'b', 'c');
alert(args.length); // 3
alert(args[0]); // 'a'

Si no se le pasa un argumento a un parametro, su valor será undefined.

function f(a, b, c) {
    console.log(a, b, c);
    console.log(arguments);
}

> f('1', '2', '3'); 
1 2 3
[ '1', '2', '3' ]

> f('a', 'b');
a b undefined
[ 'a', 'b' ]

> f();
undefined undefined undefined
[]

arguments: Parece un arreglo, pero...

La palabra clave arguments se comporta similar a un arreglo. Sin embargo no tiene los métodos de arreglos.

var sum = function() {
    return arguments.reduce(function(x,y) {
        return x + y;
    });
};
var result = sum(1,2,3); // Error!

Es posible conseguir convertir arguments a un arreglo usando Array.prototype.slice

var sum = function() {
    var args = [].slice.call(arguments);
    return args.reduce(function(x,y) {
        return x + y;
    });
};var result = sum(1,2,3); // 6</code></pre>

Parámetros: Algunas cosas son opcionales

Se puede conseguir el efecto de parametros opcionales usando el operador ||.

function arreglo(x,y) {
    x = x || 0;
    y = y || 0;
    return [x,y];
}

> arreglo();
[0,0]
> arreglo(2);
[2,0]
> arreglo(2,4);
[2,4]

Esto es posible dado que 'undefined' es convertido a su 'false' antes de evaluar la operación. Forzando la ejecución de la expresión a la derecha del operador.

x = false || 'It works!';
x; // 'It works!'

Arity: Poniendo las cosas aridas

La aridad es un concepto matemático que se refiere al número de parámetros que recibe una función.

function identity(x) { return x }; // Arity of one.
function sum(x, y) { return x + y; } // Arity of two.
function initiateWorldDomination() { /* magic */ } // Arity of zero.

Este concepto puede ser usado junto a arguments para conseguir parámetros requeridos

function sum(x, y) {
    if (arguments.length < 2) {
        throw 'At least two arguments are needed.';
    }

    return x + y;
}

Scope: Aumentando el alcance

El "scope" (o alcance/ámbito) de una variable es siempre la función en la que se encuentra. A diferencia de otros lenguajes de programación, el scope es por bloques.

function foo() {
    var x = -3;
    if (x < 0) {
        var tmp = -x;
    }
    console.log(tmp); //3
}

"tmp" se mantuvo hasta el final de la función. En otros lenguajes influenciados por C, tmp solo duraria hasta el final del if.

Block Scoping: Actuando como los demás

La manera de como funciona el scope en C/C++/C#/Java se llama Block Scoping. Se puede conseguir este efecto usando una técnica llamada IIFE.

Immediately Invoked Function Expression (IIFE)

(function () { // block start
    var tmp = '...'; // "local" variable.
})(); // block end

El paréntesis que envuelve la función evita que sea interpretada como un FS. El segundo, ejecuta inmediatamente lo devuelto por el primero. El cuerpo de la función representa un scope nuevo. Las declaraciones dentro no afectan el objeto global.

Closures: Envuelvete

Una función se mantiene atada a su entorno. Incluso después de salir de su scope. e.g: Cuando es retornada.

function createIncrementor(start, seed) {
    return function() {
        return start += seed;
    }
}
> var incByThree = createIncrementor(0,3)
> incByThree();
3
> incByThree();
6
> incByThree();
9

Bonus

Conceptos avanzados

Métodos de funciones

  • bind
  • apply
  • call

Más información

Programación funcional

  • Aplicaciones Parciales (Currying)
  • Composición (Composition)
  • Flipping
  • Point-free Style

Más información

¿Preguntas?

¡Muchas gracias!

Referencias

Written by Efrax86 

Adapted by GuyWithAfro


Made with Slides.com