93

次のような変数で再帰関数を作成できます。

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

これで、functionHolder(3);を出力します3 2 1 0。私が次のことをしたとしましょう:

var copyFunction = functionHolder;

copyFunction(3);上記のように出力3 2 1 0されます。その後、次のように変更functionHolderした場合:

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

次に、期待どおりに、functionHolder(3);を与えStop counting!ます。

copyFunction(3);これで、関数(それ自体が指す)ではなく、3 Stop counting!参照するとおりになります。functionHolderこれは状況によっては望ましい場合がありますが、関数を保持する変数ではなく、それ自体を呼び出すように関数を作成する方法はありますか?

つまり、電話をかけたときにこれらすべての手順を実行しても得られるように、回線のみを変更することは可能ですか?試しましたが、エラーが発生します。functionHolder(counter-1);3 2 1 0copyFunction(3);this(counter-1);this is not a function

4

5 に答える 5

149

名前付き関数式の使用:

関数式に、実際にはプライベートで、関数の内部からのみ表示される名前を付けることができます。

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

ここでmyselfは、関数自体の内部にのみ表示されます。

このプライベート名を使用して、関数を再帰的に呼び出すことができます。

13. Function DefinitionECMAScript5仕様を参照してください。

FunctionExpressionの識別子は、FunctionExpressionのFunctionBody内から参照して、関数がそれ自体を再帰的に呼び出すことができるようにすることができます。ただし、FunctionDeclarationとは異なり、FunctionExpressionの識別子は、FunctionExpressionを囲むスコープから参照することはできず、影響を与えることもありません。

バージョン8までのInternetExplorerは、名前が実際に囲んでいる変数環境に表示され、実際の関数の複製を参照しているため、正しく動作しないことに注意してください(以下のpatrick dwのコメントを参照)。

arguments.calleeの使用:

arguments.calleeまたは、現在の関数を参照するために使用することもできます。

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

ECMAScriptの第5版では、strictモードでのarguments.callee()の使用は禁止されています。ただし、次のようになります。

MDNから):通常のコードでは、arguments.calleeは囲んでいる関数を参照します。このユースケースは弱く、囲んでいる関数に名前を付けるだけです。さらに、arguments.calleeにアクセスした場合、インライン化されていない関数への参照を提供できるようにする必要があるため、arguments.calleeはインライン関数などの最適化を大幅に妨げます。厳密モード関数のarguments.calleeは、設定または取得時にスローされる削除不可能なプロパティです。

于 2011-08-15T12:56:58.533 に答える
10

arguments.callee [MDN]を使用して関数自体にアクセスできます。

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

ただし、これは厳密モードでは機能しません。

于 2011-08-15T12:58:02.913 に答える
6

Y-combinatorを使用できます:(ウィキペディア

// 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)));

そして、あなたはそれをこれとして使うことができます:

// 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);
  }
});
于 2015-09-29T18:24:11.473 に答える
5

これは古い質問ですが、名前付き関数式の使用を避けたい場合に使用できるもう1つの解決策を提示したいと思いました。(それらを避けるべきかどうかを言うのではなく、単に別の解決策を提示するだけです)

  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);
于 2015-05-29T23:36:43.280 に答える
3

非常に簡単な例を次に示します。

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);

の値がcounter何であるかに関して、カウントが「逆方向」であることに注意してください。slugこれは、ログを記録する前に関数が繰り返されるため、これらの値をログに記録する位置が原因​​です。したがって、ログが発生する前に、基本的にコールスタック にどんどん深くネストし続けます。

再帰が最後の呼び出しスタック項目に達すると、関数呼び出しの「外」をトランポリンしますが、最初の増分はcounter最後のネストされた呼び出しの内部で発生します。

これが質問者のコードの「修正」ではないことは知っていますが、タイトルを考えると、再帰をよりよく理解するために、再帰を一般的に例示すると思いました。

于 2014-10-18T00:07:22.467 に答える