Pregunta Llamar a una función de javascript recursivamente


Puedo crear una función recursiva en una variable como esta:

/* Count down to 0 recursively.
 */
var functionHolder = function (counter) {
    output(counter);
    if (counter > 0) {
        functionHolder(counter-1);
    }
}

Con este, functionHolder(3); saldría 3  2  1  0. Digamos que hice lo siguiente:

var copyFunction = functionHolder;

copyFunction(3); saldría 3  2  1  0 como anteriormente. Si yo luego cambiara functionHolder como sigue:

functionHolder = function(whatever) {
    output("Stop counting!");

Entonces functionHolder(3); daría Stop counting!, como se esperaba.

copyFunction(3); ahora da 3  Stop counting! como se refiere a functionHolder, no la función (que a su vez apunta). Esto podría ser deseable en algunas circunstancias, pero ¿hay alguna forma de escribir la función para que se llame a sí misma y no a la variable que la contiene?

Es decir, ¿es posible cambiar solamente la línea functionHolder(counter-1); de modo que seguir todos estos pasos todavía da 3  2  1  0 cuando llamamos copyFunction(3);? Lo intenté this(counter-1); pero eso me da el error this is not a function.


74
2017-08-15 12:51


origen


Respuestas:


Uso de expresiones de funciones con nombre:

Puede darle a una expresión de función un nombre que sea realmente privado y solo es visible desde el interior de la función ifself:

var factorial = function myself (n) {
    if (n <= 1) {
        return 1;
    }
    return n * myself(n-1);
}
typeof myself === 'undefined'

aquí myself es visible solo dentro de la función sí mismo.

Puede usar este nombre privado para llamar a la función recursivamente.

Ver 13. Function Definition de la especificación ECMAScript 5:

El identificador en una FunctionExpression se puede referenciar desde el FunctionBody de FunctionExpression para permitir que la función se llame recursivamente. Sin embargo, a diferencia de en una declaración de función, el identificador en una expresión de función no se puede referenciar y no afecta el alcance que encierra la expresión de función.

Tenga en cuenta que Internet Explorer hasta la versión 8 no se comporta correctamente ya que el nombre es realmente visible en el entorno de variables adjuntas, y hace referencia a un duplicado de la función real (ver patrick dwcomentario de abajo).

Usando arguments.callee:

Alternativamente podrías usar arguments.callee para referirse a la función actual:

var factorial = function (n) {
    if (n <= 1) {
        return 1;
    }
    return n * arguments.callee(n-1);
}

La quinta edición de ECMAScript prohíbe el uso de arguments.callee () en Modo estricto, sin embargo:

(De MDN): En el código normal arguments.callee se refiere a la función adjunta. Este caso de uso es débil: ¡simplemente nombra la función adjunta! Además, arguments.callee dificulta considerablemente las optimizaciones, como las funciones en línea, porque debe ser posible proporcionar una referencia a la función no en línea si se accede a arguments.callee. arguments.callee para funciones de modo estrictas es una propiedad no eliminable que se lanza cuando se establece o recupera.


130
2017-08-15 12:56



Puede acceder a la función usando arguments.callee  [MDN]:

if (counter>0) {
    arguments.callee(counter-1);
}

Sin embargo, esto se romperá en modo estricto.


8
2017-08-15 12:58



Sé que esta es una vieja pregunta, pero pensé que presentaría una solución más que podría usarse si desea evitar el uso de expresiones de función nombradas. (No dice que debe o no debe evitarlos, simplemente presentando otra solución)

  var fn = (function() {
    var innerFn = function(counter) {
      console.log(counter);

      if(counter > 0) {
        innerFn(counter-1);
      }
    };

    return innerFn;
  })();

  console.log("running fn");
  fn(3);

  var copyFn = fn;

  console.log("running copyFn");
  copyFn(3);

  fn = function() { console.log("done"); };

  console.log("fn after reassignment");
  fn(3);

  console.log("copyFn after reassignment of fn");
  copyFn(3);

5
2018-05-29 23:36



Puedes usar el Y-combinator:Wikipedia)

// ES5 syntax
var Y = function Y(a) {
  return (function (a) {
    return a(a);
  })(function (b) {
    return a(function (a) {
      return b(b)(a);
    });
  });
};

// ES6 syntax
const Y = a=>(a=>a(a))(b=>a(a=>b(b)(a)));

// If the function accepts more than one parameter:
const Y = a=>(a=>a(a))(b=>a((...a)=>b(b)(...a)));

Y puedes usarlo así:

// ES5
var fn = Y(function(fn) {
  return function(counter) {
    console.log(counter);
    if (counter > 0) {
      fn(counter - 1);
    }
  }
});

// ES6
const fn = Y(fn => counter => {
  console.log(counter);
  if (counter > 0) {
    fn(counter - 1);
  }
});

4
2017-09-29 18:24



Aquí hay un ejemplo muy simple:

var counter = 0;

function getSlug(tokens) {
    var slug = '';

    if (!!tokens.length) {
        slug = tokens.shift();
        slug = slug.toLowerCase();
        slug += getSlug(tokens);

        counter += 1;
        console.log('THE SLUG ELEMENT IS: %s, counter is: %s', slug, counter);
    }

    return slug;
}

var mySlug = getSlug(['This', 'Is', 'My', 'Slug']);
console.log('THE SLUG IS: %s', mySlug);

Tenga en cuenta que counter cuenta "hacia atrás" con respecto a qué slugEl valor de Esto se debe a la posición en la que estamos registrando estos valores, ya que la función recurre antes de iniciar sesión, así que, esencialmente, seguimos anidando cada vez más en el pila de llamadas  antes de el registro se lleva a cabo.

Una vez que la recursión se encuentra con el elemento final de la pila de llamadas, trampolines "fuera" de las llamadas de función, mientras que, el primer incremento de counter ocurre dentro de la última llamada anidada.

Sé que esto no es una "solución" en el código del Interlocutor, pero dado el título, pensé que genéricamente ejemplificaría Recursión para una mejor comprensión de la recursión, francamente.


3
2017-10-18 00:07