Capítulo 3
Funciones

La gente piensa que las ciencias de la computación son las artes de los genios pero la realidad es lo contrario, es sólo un montón de gente haciendo cosas que se construyen sobre otras, como una pared de piedras muy pequeñas.

Donald Knuth

Has visto valores función, como alert, y cómo llamarlos. Las funciones son el pan de cada día en la programación en JavaScript. El concepto de de envolver una porción del programa en un valor(variable) tiene muchos usos. Es una herramienta para estructurar programas más grandes, para reducir la repetición, para asociar nombres con subprogramas, y para separar estos programas de los demás.

La aplicación más obvia de las funciones es la de definir un nuevo vocabulario. Crear nuevas palabras en la prosa regular en lenguaje humano es usualmente un mal estilo. Pero en programación, es indispensable.

Un adulto promedio tiene unas 20,000 palabras en su vocabulario. Pocos lenguajes de programación tienen 20,000 comandos incorporados. Y el vocabulario que está disponible tiende a ser definido de forma más precisa, así que es menos flexible que en un lenguaje humano. En consecuencia, usualmente tenemos que añadir algo de nuestro propio vocabulario para evitar repetirnos demasiado.

Definiendo una función

Una definición de una función es sólo una definición regular de una variable cuando ocurre que el valor dado a la variable es una función. Por ejemplo, el siguiente código define la variable cuadrado para referirse a la función que produce el cuadrado de un número dado:

var cuadrado = function(x) {
  return x * x;
};

console.log(cuadrado(12));
// → 144

Una función es creada por una expresión que empieza con la palbra reservada function. Las funciones tienen un conjunto de parametros (en este caso sólo x) y un cuerpo, que contiene las sentencias que serán ejecutadas cuando la función sea llamada. El cuerpo de la función tiene que estar siempre encerrado en llaves, incluso cuando consista de una sola instrucción (como en el ejemplo previo).

Una función puede tener varios parámetros o puede no tener ninguno. En el siguiente ejemplo hazRuido no tiene parámetros, mientras que potencia tiene dos:

var hazRuido = function() {
  console.log("Pling!");
};

hazRuido();
// → Pling!

var potencia = function(base, exponente) {
  var resultado = 1;
  for (var cuenta = 0; cuenta < exponente; cuenta++)
    resultado *= base;
  return resultado;
};

console.log(potencia(2, 10));
// → 1024

Algunas funciones producen un valor, como potencia y cuadrado, y algunas no, como hazRuido, la cuál produce sólo un efecto secundario. Una sentencia return determina el valor que una función regresa. Cuando el control pasa a esta sentencia, inmediatamente salta fuera de la función actual y pasa el valor retornado a la código que llamó la función. La palabra reservada return sin una expresión después de ella hará que la función devuelva undefined.

Parámetros y ámbitos

Los parametros para una función se comportan como variables normales, pero su valor inicial esta dado por quien llama a la función, no por el código mismo de la función.

Un propiedad importante de las funciones es que las varibales creades dentro de ellas, incluyendo sus parámetros, son locales para la función. Este significa, por ejemplo, que la varibale resultado en el ejemplo potencia será creada de nuvo cada vez que la función es llamada, y estas instancias separadas no interfieren entre ellas.

Esta "localidad" de las variables aplica sólo a los parámetros y variables declaradas con la palabra reservada var dentro del cuerpo de la función. Las variables declaradas duera de cualquier función son llamadas globales, porque son visibles a través de todo el programa. Es posible acceder a estas variables desde dentro de una función, mientras no hayas declarado una variable local con el mismo nombre.

El siguiente código demuestra eso. Define y llama dos funciones que asignan un valor a la variable x. La primera declara la variable como local y de esta manera cambia únicamente la variable que creó. La segunda no declara x localmente, así que la x dentro de ella hace referencia a la x definida al principio del ejemplo.

var x = "fuera";

var f1 = function() {
  var x = "dentro de f1";
};
f1();
console.log(x);
// → fuera

var f2 = function() {
  x = "dentro de f2";
};
f2();
console.log(x);
// → dentro de f2

Este comportamiento ayuda a prevenir interferencia accidental entre las funciones. Si todas las variables fueran compartidas por el programa entero, sería muy difícil asegurarse de que algún nombre no fue usado para dos propósitos diferentes. Y si reusaste un nombre de variable, podrías ver efectos extraños de código no relacionado causando problemas en el valor de tu variable. Al tratar tus variables locales a la función, el lenguaje hace posible leer y entender las funciones como un pequeños universos, sin tener que preocuparse de todo el código a la vez.

Ámbitos Anidados

JavaScript distingue nos solo entre variables globales y locales Las funciones pueden ser creadas dentro de otras funciones, produciendo distindos grados de localidad.

Por ejemplo, esta función sin sentido, tiene dos funciones dentro de ella:

var landscape = function() {
  var resultado = "";
  var meseta = function(tamano) {
    for (var cuenta = 0; cuenta < size; cuenta++)
      resultado += "_";
  };
  var montana = function(tamano) {
    result += "/";
    for (var cuenta = 0; cuenta < size; cuenta++)
      resultado += "'";
    resultado += "\\";
  };

  meseta(3);
  montana(4);
  meseta(6);
  montana(1);
  meseta(1);
  return resultado;
};

console.log(landscape());
// → ___/''''\______/'\_

Las funciones meseta y montana pueden "ver" la variable llamada resultado, debido a que están dentro de la función que la define. Pero no pueden ver la variable cuenta entre ellas, porque están definidas fuera del ámbito de la otra. El entorno fuera de la función paisaje no puede acceder a ninguna de las variables definidas dentro de paisaje.

En pocas palabras, cada ámbito local también puede ver los ámbitos locales que lo contienen. El conjunto de variables visible dentro de la función es determinado por el lugar de la función en el texto del programa. Todas las variables de los bloques que envuelven una la definición de una función son visibles para ella, esto es, en todos los cuerpos de las funciones que la envuelven y las correspondientes al nivel superior(ámbito global). Este manera de resolver la visibilidad de las variables es llamada definición léxica de ámbito(lexical scoping).

Las personas que tienen experiencia con otros lenguajes de programción podrían esperar que cualquier bloque entre llaves produzca un nuevo entorno local. Pero en JavaScript, las funciones son lo único que crea un nuevo ámbito. Pero puedes usar bloques independientes.

var algo = 1;
{
  var algo = 2;
  // Haz algo con la variable...
}
// Fuera del bloque...

Pero la variable algo dentro del bloque se refiere a la misma variable que la que está fuera del bloque. De hecho, aunque bloques como este son permitidos, sólo son útiles para agrupar el cuerpo de las sentencias if o de los bucles.

Si esto te parece raro, no eres el único. La próxima versión de JavaScript introducirá una palabra reservada let, que trabaja como var pero crea una variable que es local para el bloque en el que está, no para la función.

Funciones como valores

Las variables de función normalmente actúan como nombres para una parte específica del programa. Esa variables es definida una vez y nunca cambiada. Esto hace fácil empezar a confundir la función con su nombre.

Pero son diferentes entre sí. Un valor función puede hacer todo los que los otros valores pueden hacer; lo puedes usar en expresiones arbitrarias, no sólo llamarlos. Es posible guardar la función en un nuevo lugar, pasar como argumento a otra función, etc. De manera parecida, la variable que contiene una función sigue siendo una variable normal a la que se le puede asignar otro valor, como sigue:

var lanzarMisiles = function(valor) {
  sistemaDeMisiles.lanzar("ahora");
};
if (modoSeguro)
  lanzarMisiles = function(valor) {/* No hacer nada. */};

En Capítulo 5, hablaremos de las maravillosas cosas que puedes hacer al pasar valores función a otras funciones.

Notación de Declaración

Existe una forma más corta de decir “var square = function…”. La palabra reservada function puede ser usada al principio de una sentencia, como sigue:

function cuadrado(x) {
  return x * x;
}

Esta es una declaración de función. La expresión define la variable cuadrado y la apunta a la función dada. Hasta aquí todo bien. sin embargo, hay una pequeña sutileza con esta forma de definición.

console.log("El futuro dice: ", futuro());

function future() {
  return "Seguimos sin tener carros voladores.";
}

Este código funciona aunque la función está definida debajo del código que la usa. Esto es debido a que las declaraciones de función no toman parte en el flujo de control regular de arriba hacia abajo. Son movidas conceptualmente a la parte superior de su ámbito y pueden ser usadas por todo el código en ese ámbito. Esto es útil algunas veces porque nos da la libertad de organizar el código de una manera parezca significativa sin preocuparnos por definir todas las funciones antes de su primer uso.

¿Qué pasa cuando pones una declaración de función dentro de un bloque condicional (if) o dentro de un bucle? Bueno, mejor no lo hagas. Diferentes plataformas de JavaScript en diferentes navegadores hacen diferentes cosas tradicionalmente en esa situación, y el último estándar de hecho lo prohibe. Si quieres que tus programas sean consistentes, usa las sentencia de declaración de función en el bloque más externo de tu función o programa.

function ejemplo() {
  function a() {} // Bien
  if (alguna_condicion) {
    function b() {} // ¡Peligro!
  }
}

La pila de llamadas

Será útil mirar más de cerca la forma en que el control se mueve a través de las funciones. Aquí hay un simple programa que hace unas cuantas llamadas a funciones.

function saluda(a_quien) {
  console.log("Hola " + a_quien);
}
saluda("Harry");
console.log("Adiós");

(((console.log))Una ejecución de este programa va más o menos así: la llamada a saluda causa que el control pase al inicio de esa función (línea 2). Esta llama a control.log (una función incluída en los navegadores), que toma el control, hace su trabajo, y devuelve el control a la línea 2. Después, se alcanza el dinal de la función saluda, así que se regresa al lugar en dónde se llamó, en la línea 4. La línea siguiente llama a console.log otra vez.

Podemos mostrar el flujo de control esquemáticamente así:

raíz
   saluda
        console.log
   saluda
raíz
   console.log
raíz

Debido a que una función tiene que saltar de regreso al lugar en que fue llamada cuando termine, la computadora debe recordar el contexto en el que fue llamada. En un caso, console.log tiene que regresar a la función saluda. En el otro caso salta al final del programa.

El lugar en el que la computadora guarda este context es la pila de llamadas. Cada vez que una función es llamada, el contexto actual es puesto en la parte superior de esta pila. Cuando la función retorne, remueve el contexto superior de la "pila" y lo usa para continuar la ejecución.

Guardar esta pila requiere espacio en la memoria de la coputadora. Cuando la pila se hace demasiado grande la computadora mostrará un error parecido a "out of stack space" (sin espacio en la en la pila) o "too much recursion" (demasiada recursión). El siguiente código lo ilustra al preguntarle algo realmente difícil a la computadora, lo que causa un ir y venir infinito entre funciones. Más bien, sería infinito, si la computadora tuviera una pila infinita. Como son las cosas, nos quedaremos sin espacio, o "volaremos la pila".

function gallina() {
  return huevo();
}
function huevo() {
  return gallina();
}
console.log(gallina() + " fue primero");
// → ??

Argumentos Opcionales

El siguiente código es permitido y se ejecuta sin ningún problema:

alert("Hola", "Buenas Noches", "¿Cómo estás?");

La función alert oficialmente acepta sólo un argumento. Aún así, cuando la llamas como aquí, no se queja. Simplemente ignora los otros argumentos y te muestra "Hola".

JavaScript es extremadamente abierta de mente acerca del número de argumentos que le pasas a una función. Sí le pasas demasiados, loa argumentos extra son ignorados. Si le pasas muy pocos, los parámetros quie faltan simplemente son asignados a undefined.

El lado malo de esto es que es posible, probable a menudo, que le pasarás accidentalmente un número incorrecto de argumentos a las funciones y nadie te avisará.

El lado bueno de este comportamiento es que puede ser usado para tener una función que tome parámetros "opcionales". Por ejemplo, la siguiente versión de potencia puede ser llamada con dos argumentos o uno solo, caso en el que el exponente se asume como dos, y la función se comporta como cuadrado.

function potencia(base, exponente) {
  if (exponente == undefined)
    exponente = 2;
  var resultado = 1;
  for (var cuenta = 0; cuenta < exponente; cuenta++)
    resultado *= base;
  return resultado;
}

console.log(potencia(4));
// → 16
console.log(potencia(4, 3));
// → 64

En el próximo capítulo, veremos una forma en la que el cuerpo de una función puede obetener la lista exacta de argumentos que se le pasaron. Esto es útil porque permite a una función aceptar un número indeterminado de argumentos. Por ejemplo, console.log hace uso de esto: imprime todos los valores que se le pasaron.

console.log("R", 2, "D", 2);
// → R 2 D 2

Closure

La habilidad de tratar Funciones como valores, combinada con el hecho de que las varibles locales son "re-creadas" cada vez que una función es llamada, saca a la luz una pregunta interesante. ¿Qué pasa con las variables locales cuando la función que las creó ya no está activa?

El siguiente código muestra un ejemplo de esto. Define una funnción, envuelveValor, que crea una variable local. Después devuelve una función que accede y devuelve esta variable local.

function envuelveValor(n) {
  var variableLocal = n;
  return function() { return variableLocal; };
}

var envoltura1 = envuelveValor(1);
var envoltura2 = envuelveValor(2);
console.log(envoltura11());
// → 1
console.log(envoltura2());
// → 2

Esto está permitido y funciona como esperarías; la variable todavía puede leerse. De hecho, múltiples instancias de las variables pueden existir al mismo tiempo, lo que es otra buena ilustración del concepto de que las variables locales son re-creadas realmente para cada llamada; diferentes llamadas no pueden afectar otras variables locales.

Esta característica -ser capaces de hacer referencia a una instancia local de varables en un función que las encierra- se llama closure. Una función que "encapsula" algunas variables locales es llamada una closure. Este comportamiento no sólo te libera de preocuparte de los tiempos de vida de las variables, además permite algunos usos creativos de las funciones.

Con un pequeño cambio, podemos podemos hacer del ejemplo anterior funciones que multiplicquen por un número arbitrario.

function multiplicador(factor) {
  return function(numero) {
    return numero * factor;
  };
}

var doble = multiplicador(2);
console.log(doble(5));
// → 10

La variable explícita variableLocal de la función envuelveValor en el ejemplo previo no es necesaria porque un parámetro en sí mismo es una variable local.

Concebir los parámetros de esta forma requiere algo de práctica. Un buen modelo mental es pensar en la palabra clave function como si "congelara" el código que está dentro de ella en un paquete(el valor función). Así, cuando leas return function(...){...}, piensa en que esto regresa un acceso a un conjunto de cálculos, congelados para uso posterior.

En el ejemplo, multiplicador regresa un pedazo congelado de código que se guarda en la variable doble. La última línea entonces llama el valor guardado en esta variable, haciendo que el código congelado (return numero * factor;) se active. Este todavía tiene acceso a la variable factor de la llamada a multiplicador que lo creó, y además obtiene acceso al argumento que se pasa cuando se activa el código, 5, a través de su parámetro numero.

Recursión

Es perfectamente correcto que una función se llame a sí misma, mientras tenga cuidado de no desbordar la pila. Una función que se llama a sí misma se llama recursiva. La recursión permite que algunas funciones se escriban con un estilo diferente. Tomemos por ejemplo, esta implementación alternativa de potencia:

function potencia(base, exponente) {
  if (exponente == 0)
    return 1;
  else
    return base * potencia(base, exponente - 1);
}

console.log(potencia(2, 3));
// → 8

Esto es más cercano al modo en que los matemáticos definen la potenciación y describe el concepto de un modo más elegante que la variante que lo hace con bucles. La función se llama a sí misma varias veces con diferentes argumentos para conseguir la multiplicación repetida.

Pero esta implementación tiene un problema importante: en implementaciones típicas de JavaScript, es cerca de 10 veces más lenta que la versión con bucles. Correr a través de un siple bucle es más barato que llamar a una función muchas veces.

El dilema de velocidad contra elegancia es interesante. Puedes verlo como un continuum entre amigabilidad-humano y amigabilidad-máquina. Casi cualquier programa puede hacerse más rápido haciéndolo más grande y convolucionado. El programador debe de decidir el balance apropiado.

En el caso de la función anterior potencia la poco elegante versión(iterativa) es aún bastante simple y fácil de leer. No tiene mucho sentido reemplazarla con la versión recursiva. A menudo, sin embargo, un programa tiene conceptos tan complejos que sacrificar un poco de eficiencia para hacer el programa más claro se vuelve una opción atractiva.

La regla básica, que ha sido repetida por muchos programadores y con la cuál concuerdo de todo corazón, es no preocuparse por la eficiencia hasta que estés seguro que el programa es demasiado lento. Si lo es, busca las partes que están abarcando la mayoría del tiempo y empieza a cambiar elegancia por eficiencia en esas partes.

Por supuesto, esta regla no significa que debas ignorar el rendimiento completamente. En muchos casos, como en la función potencia, no se gana demasiada simplicidad de la solución "elegante". Y algunas veces un programador experimentado puede inmediatamente que un soulución simple nunca va a ser lo suficientemente rápida.

La razón por la que estoy hablando tanto de esto es que muchos programadores novatos se enfocan fanáticamente en la eficiencia, incluso en los detalles más pequeños. El resultado son programas más grandes, más complicados y a menudo menos correctos, que toman más tiempo en escribirse que sus equivalentes más sencillos y que generalmente corren solo un poco más rápido.

Pero la recursión no es siempre sólo una alternativa menos eficiente a los bucles. Alguos problemas son mucho más fáciles de resolver con recursión que con bucles. La mayoría de estos son problemas que requieren explorar o procesar varias "ramas", cada una de las cuáles se puede ramificar otra vez.

Considera el siguente rompecabezas: empezando por el número 1 y añadiendo repetidamente 5 o multiplicándolo por 3, un número infinito de nuevos números puede ser producido. ¿Cómo escribirías una función que, dado un número, trate de encontrar una secuencia de sumas y multipliclaciones que producen ese número? Por ejemplo, el número 13 puede ser producido al multiplicar por 3 primero y después sumar 5 dos veces, mientras que el número 15 no puede ser producido.

Aquí hay una solución recursiva:

function encontrarSolucion(objetivo) {
  function encontrar(inicio, historia) {
    if (inicio == objetivo)
      return historia;
    else if (inicio > objetivo)
      return null;
    else
      return encontrar(inicio + 5, "(" + historia + " + 5)") ||
             encontrar(inicio * 3, "(" + historia + " * 3)");
  }
  return encontrar(1, "1");
}

console.log(encontrarSolucion(24));
// → (((1 * 3) + 5) * 3)

Nota que este programa no encuentra necesariamente la ruta más corta de operaciones. Se satisface cuando cuentre cualquier sequencia.

No necesariamente espero que veas como este trabaja esto inmediatamente. Pero trabajemos en eso, porque es un gran ejercicio en el pensamiento recursivo.

La función interna encontrar hace la recursividad. Toma dos argumentos – el número actual y una cadena que registra como hemos alcanzado el número – y regresa o una cadena que muestra como llegar al objetivo o null.

Para hacer esto la función realiza una de tres acciones. Si el número actual es el número objetivo, entonces la historia actual es una forma de alcanzar este objetivo, así que siemplemente es devuelta. Si el número actual es mayot que el número objetivo, no tiene sentido seguir haciendo más exploraciones porque tanto sumar como multiplicar sólo hará mayor el número. Y finalmente, si estamos todavía debajo del objetivo la función prueba los dos caminos posibles que empiezan con el número actual llamándose dos veces a sí misma, una vez por cada uno de los pasos permitos. Si la primera llamada regresa cualquier cosa que no sea null, se devuelve como resultado. De otra forma, la segunda opción es la que se devuelve, independientemente de si produce una cadena o null.

Para entender mejor como esta función produce el efecto que estamos buscando, miremos a todas las llamadas a encontrar que se hacen cuando estamos buscando la solución para el número 13.

encuentra(1, "1")
  encuentra(6, "(1 + 5)")
    encuentra(11, "((1 + 5) + 5)")
      encuentra(16, "(((1 + 5) + 5) + 5)")
        demasiado grande
      encuentra(33, "(((1 + 5) + 5) * 3)")
        demasiado grande
    encuentra(18, "((1 + 5) * 3)")
      demasiado grande
  encuentra(3, "(1 * 3)")
    encuentra(8, "((1 * 3) + 5)")
      encuentra(13, "(((1 * 3) + 5) + 5)")
        ¡Encontrado!

El sangrado (indentación) indica la profundidad en la pila de llamadas. La primera vez que encuentra es llamada, se llama dos veces a sí misma para explorar las soluciones que empiezan con (1 + 5) y (1 * 3). La primera llamada intenta encontrar una solución que empiece con (1 + 5) y, usando la recursión, explora todas las soluciónes que produzcan un número menor o igual que el número buscado. Dado que no encuentra una solución que corresponda con el objetivo, regresa null a la primera llamada. Entonces el operador || ahce que la llamada que explora (1 * 3) suceda. Esta búsqueda tiene más suerte debido a que su primera llamada recursiva, a través de otra llamada recursiva, llega al número que buscamos, 13. Esta llamada recursuva, la más interna, regresa una cadena y cada uno de los operadores || en la llamada superior inmediata pasan esa cadena, finalmente regresando nuestra solución.

Funciones crecientes

Existen dos formas más o menos naturales de intorducir las funciones en los programas.

La primera es que te encuentras a ti mismo escribiendo el mismo código varias veces. Necesitamos evitar hacer debido a que más código significa más espacio para que los errores se oculten y más material para leer para que leer para las personas que quiere entender el programa. Así que tomamos funcionalidad repetida, encontramos un buen nombre para esta, y la ponemos dentro de una función. La segunda forma es que encuentres que necesitas cierta funcionalidad que no has escrito aún y que suena como a que merece su propia función. Empezarás por nombrar la función y después escribirás su cuerpo. Podrías incluso empezar a escribir el código que usa la función antes de realmente definir la función misma.

Qué tan difícil es encontrar un nombre para una función es un buen indicador de que tan claro tienes el concepto de aquello que estás tratando de envolver. Hagamos un ejemplo.

Necesitamos escribir un problema que imprima dos números, el número de las vacas y de los pollos de una granja, con las palabras Cows y Pollos después de estos, y completados con ceros para que siempre sean de 3 dígitos de longitud.

007 Vacas
011 Pollos

Esto claramente requiere una función de dos argumentos. Empecemos a programar.

function imprimeInventarioGranja(vacas, pollos) {
  var vacasString = String(vacas);
  while (vacasString.length < 3)
    vacasString = "0" + vacasString;
  console.log(vacasString + " Vacas");
  var pollosString = String(pollos);
  while (pollosString.length < 3)
    pollosString = "0" + pollosString;
  console.log(pollosString + " Pollos");
}
imprimeInventarioGranja(7, 11);

Añadir .length despues de un valor de cadena nos dará la longitud de esa cadena. Así, los while continuán agregando ceros al principio de la cadena del número hasta que son por lo menos de 3 caracteres.

¡Misión cumplida! Pero casi cuando le vamos a mandar el código al granjero (con una buena factura) nos llama y nos dice que ha empezado a críar puercos, y que si podríamos extender el software para también mostrar los puerquitos, ¿por favor?.

De seguro podemos. Pero mientras estamos en el proceso de copiar y pegar esas cuatro líneas una vez más, paramos y reconsideramos. Existe una mejor forma. Aquí hay un intento:

function imprimeConCerosYEtiqueta(numero, etiqueta) {
  var numeroString = String(numero);
  while (numeroString.length < 3)
    numeroString = "0" + numeroString;
  console.log(numeroString + " " + etiqueta);
}

function imprimeInventarioGranja(vacas, pollos, puercos) {
  imprimeConCerosYEtiqueta(vacas, "Vacas");
  imprimeConCerosYEtiqueta(pollos, "Pollos");
  imprimeConCerosYEtiqueta(puercos, "Puercos");
}

imprimeInventarioGranja(7, 11, 3);

¡Funciona! Pero ese nombre, imprimeConCerosYEtiqueta, es un poco feo. Amonotona tres cosas–imprimir, completar con ceros, y agragar la etiqueta–en una sola función.

En vez de quitar la parte repetida de nuestra programa por mayoreo, tratemos de escoger un solo concepto.

function rellenoConCero(numero, ancho) {
  var cadena = String(numero);
  while (cadena.length < ancho)
    cadena = "0" + cadena;
  return cadena;
}

function imprimeInvantarioGranja(vacas, pollos, puercos) {
  console.log(rellenoConCero(vacas, 3) + " Vacas");
  console.log(rellenoConCero(pollos, 3) + " Pollos");
  console.log(rellenoConCero(puercos, 3) + " Puercos");
}

imprimeInvantarioGranja(7, 16, 3);

Una función con un bonito y obvio nombre como rellenoConCero hace más fácil para alguien que lea el código descubrir lo que hace. Y eso es útil en más situaciones que sólo en este programa específico. Por ejemplo, puedes usarla para imprimir una tabla bien alineada de números.

¿Qué tan inteligente y versátil debería ser nuestra función? Podríamos escribir cualquier cosa desde una función terriblemente simple que sólamente rellena un número de tal manera que tenga 3 caracteres hasta una complicada, muy generalizada función, un sistema de formateo de números que maneje fracciones, números negativos, alineación de puntos, relleno con diferentes carecteres y así por el estilo.

Un principio útil no añadir características a menos que estés completamente seguro de que las vas a ocupar. Puede ser tentador escribir marcos de trabajo generals "frameworks" para cada pequeño pedazo de funcionalidad que te encuentras. Resiste el impulso. No acabarás ningún trabajo real y terminarás escribiendo mucho código que nadie usará alguna vez.

Funciones y efectos colaterales

Las funciones pueden ser más o menos divididas en aquellas que son llamadas por sus efectos colaterales y aquellas que son llamadas por su valor de resultado. (Aunque es completamente posible tener tanto efectos colaterales como retornar un valor).

La primer función de ayuda en el ejemplo de la granja, imprimeConCerosYEtiqueta, es llamada por su efecto colateral: imprime su valor de resultado. La segunda versión, rellenoConCero, es llamada por su valor de resultado. No es coincidencia que la segunda sea útil en más situaciones que la primera. Las funciones que crean valores son más fáciles de combinar en nuevas formas que funciones que realizan directamente un efecto colateral.

Una función pura es un tipo de función que produce un valor que no sólo carece de efectos colaterales, tampoco usa los efectos colaterales de otro código–por ejemplo, no lee variables globales que son ocasionalmente cambiadas por otro código. Una función pura tiene la agradable propiedad de que, cuando se llama los mismos argumentos, siempre produce el mismo valor (y no hace nada más). Esto hace fácil razonar con ella. Una llamada a una función como esta puede ser sustituida mentalmente como su resultado, sin cambiar el significado del código. Cuando no estas seguro de que una función pura funciona correctamente, puedes probarlo simplemente llamándola, y sabiendo que trabaja bien este contexto, trabajará bien en cualquier context. Funciones no puras pueden regresar diferentes valores basadas en todo tipo de factores y tienen efectos secundarios y que pueden ser difícil de probar y de razonar con ellas.

Aún así, no hay necesidad de sentirse mal cuando escribimos funciones que no son puras o de armar una guerra santa para eliminarlas de tu código. Los efectos secundarios a menudo son útiles. No habría forma de imprimir una versión pura de console.log, por ejemplo, y console.log es ciertamente útil. Algunas operaciones son además más fáciles de expresar en una forma eficiente cuando se usan los efectos scundarios, así que la velocidad de cómputo puede ser una razón para evitar la pureza.

Sumrio

Este capítulo enseñó como escribir tus propias funciones. La palabra resevada function, cuando se usa como una expresión, puede crear un valor función. Cuando es usada como sentencia, puede ser usada para declarar una variable y darle una función como valor.

// Crear un valor función f
var f = function(a) {
  console.log(a + 2);
};

// Declarar g como función
function g(a, b) {
  return a * b * 3.5;
}

Un aspecto clave para entender las funciones es entender los ámbitos locales. Los parámetros y variables declaradas dentro de una función son locales para la función, re-creados cada vez que la función es llamada y no visibles de desde fuera. Las funciones declaradas dentro de otra función tienen acceso al ámbito local externo de la función.

Seprar las tareas que tu tarea realiza en diferentes funciones es útil. No tendrás que repetir lo mismo tanto, y las funciones pueden hacer un programa más legible al agrupar el código en partes conceptuales, de la misma forma que capítulos y secciones ayudan a organizar el texto regular.

Ejercicios

Mínimo

El capítulo anterior intordujo la función estándar Math.min que devuelve su argumento más pequeño. Ahora nosotros mismos podemos hacer eso. Escribe una función min que tome dos argumentos y devuelva el mínimo.

// Tu código va aquí.

console.log(min(0, 10));
// → 0
console.log(min(0, -10));
// → -10

Si tienes problemas poniendo las llaves y los paréntesis en el lugar correcto para obtener una definición de función válida, empieza por copiar uno de los ejemplos del capítulo y modificarlo.

Una función puede contener múltiples return.

Recursión

Hemos visto que % (el operador de sobrante) puede ser usado para probar si un número es par o impar usando % 2 para checar si es divisible por dos. Aquí hay otra forma de definir si un número entero es par o impar:

Escribe una función recursiva esPar que corresponda a esta descripción. La función debería aceptar un numero como parámetro y regresar un Booleano.

Pruebala con 50 y 75. Observa cómo se comporta con -1. ¿Por qué? ¿Puedes pensar en alguna forma de componer esto?

// Tu código va aquí.

console.log(esPar(50));
// → true
console.log(esPar(75));
// → false
console.log(esPar(-1));
// → ??

La función será algo similar al encuentra interno en la solución recursiva ejemplo encuentraSolucion en este capítulo, con una cadena de if/else if/else que probará cuál de los tres casos aplica. El else final, correspondiente al tercer caso, hace la llamada recursiva. Cada una de las ramas debe de contener una sentencia return o de alguna otra forma arrglárselas para devolver un valor específico.

Cuando se le da un número negativo, la función va llamarse a sí misma una y otra vez, pasándose un número cada vez más negativo, así alejándose más y más de regresar una solución. En algún momento se quedará sin espacio en la pila y abortará.

Contando Frijoles

Puedes obtener el n-avo caracter, o letra, de una cadena escribiendo "cadena".charAt(N), similar a como obtienes su longitud con "cadena".length. El valor obtenido será una cadena que contiene sólo un caracter(por ejemplo, "b"). El primer caracter tiene la posición cero, lo cual hace que el último pueda ser encontrado en la posición string.lenght -1. En otras palabras, una cadenas de dos caracteres tiene un “lenght” o longitud de 2, y sus caracteres tienen las posiciones 0 y 1.

Escribe una función cuentaFs que tome una cadena como su único argumento y regrese un número que indique cunántos caracteres “F” mayúscula hay en la cadena.

A continuación, escribe una funcióon llamada cuentaCaracter que se comoporte como cuentaFs, con la diferencia de que tome un segundo caracter que indique el caracter que será contado(en vez de sólo caracteres “F”). Reescribe cuentaFs para hacer uso de esta nueva función.

// Tu código va a aquí.

console.log(cuentaFs("FFC"));
// → 2
console.log(cuentaCaracter("ferrocarril", "r"));
// → 4

Un bucle en tu función tendrá que revisar cada caracter en la cadena corriendo un índice desde cero hasta uno menos que su longitud (< cadena.length). Si el caracter de la posición actual es el mismo que la función está buscando, añade uno a la variable contador. Una vez que se ha terminado el bucle, el contador puede ser regresado.

Pon atención en hacer todas las variables usadas en la función locales a la función midiante el uso de la palabra reservada var.