ホームページ > 記事 > ウェブフロントエンド > Javascriptの関数宣言と再帰呼び出しの詳細説明
JavaScript 関数を宣言して呼び出す方法は、すでに使い古された決まり文句ですが、これと同じようなこともあります。一度言ったら、私ももう一度言います。本やブログで Javascript 関数を呼び出す方法が 4 つあるのを見るたびに、Kong Yiji のことを思い出します。「ウイキョウ」という単語の書き方は 4 つあります。あなたが作ったのですか?
欠点はあるものの、JavaScript は魅力的です。 Javascript の多くの美しい機能の中心となるのは、ファーストクラスのオブジェクトとしての関数です。関数は他の通常のオブジェクトと同様に作成され、変数に割り当てられ、引数として渡され、値を返し、プロパティとメソッドを保持します。関数は、トップレベルのオブジェクトとして、JavaScript に強力な関数プログラミング機能を提供するだけでなく、制御が容易ではない柔軟性ももたらします。
1. 関数宣言
変数宣言は、最初に匿名関数を作成し、それを指定された変数に代入します:
var f = function () { // function body };
通常、右辺の式のスコープかどうかを気にする必要はありません。等号の等号はグローバルまたは確実です。 クロージャ内では、等号の左側にある変数 f を通じてのみ参照できるため、考慮すべきは変数 f のスコープです。関数への f の参照が破棄され (f = null)、関数が他の変数またはオブジェクト プロパティに割り当てられていない場合、匿名関数はすべての参照を失うため、ガベージ コレクション メカニズムによって破棄されます。
関数式を使用して関数を作成することもできます:
function f() { // function body }
変数式とは異なり、この宣言メソッドは関数の組み込み属性名に値を割り当てます。同時に、現在のスコープ内の同じ名前の変数に関数を割り当てます。 (関数の name 属性、configurable、enumerable、writable はすべて false です)
function f() { // function body } console.log(f.name); // "f" console.log(f); // f()
JavaScript の変数には特別な機能があります。つまり、変数の宣言は前倒しされ、関数の宣言は関数全体の定義も先頭に追加されるため、関数が定義される前にそれを使用できます:
console.log(f.name); // "f" console.log(f); // f() function f() { // function body }
関数式の宣言はスコープの最上位にホイストされます。次のコードを試してください。
var a = 0; console.log(a); // 0 or a()? function a () {}
Crockford は、関数を宣言するために常に最初の方法を使用することを推奨しています。彼は、2 番目の方法では、関数を最初に宣言してから使用する必要があるという要件が緩和され、混乱を招く可能性があると考えています。 。 (クロックフォードは、ラッセルがウィトゲンシュタインを比較するために用いた「良心芸術家」と似た「良心プログラマー」です。この文は非常に発音が難しいです)
機能宣言
function f() {}
見なさい 起きなさいは
var f = function f(){};
の略語です。
。
var a = function b(){};
の式は、関数を作成して組み込みのname属性を「b」に代入し、この関数を変数aに代入することで外部から呼び出すこともできますが、 b() を使用することはできません。関数は a に割り当てられているため、var b = a を使用して変数 b を宣言しない限り、変数 b は自動的に作成されません。もちろん、この関数の名前は「a」ではなく「b」です。
Function コンストラクターを使用して関数を作成することもできます:
var f = new Function("a,b,c","return a+b+c;");
このメソッドは実際にグローバル スコープで匿名関数を生成し、それを変数 f に割り当てます。
2. 再帰呼び出し
再帰は、関数本体でそれ自体を呼び出す必要がある多くの問題を単純化するために使用されます。上記の変数宣言の場合、 f は変数なので、その値は簡単に置き換えることができます。
// 一个简单的阶乘函数 var f = function (x) { if (x === 1) { return 1; } else { return x * f(x - 1); } };
関数は値であり、 fn に割り当てられており、 fn( 5 を使用することが予想されます) ) 値の計算はできますが、変数 f が関数内で参照されたままのため、正常に動作しません。
関数宣言は良くなったように見えますが、残念です:var fn = f; f = function () {};
再帰関数を定義したら、変数の名前を簡単に変更しないように注意する必要があるようです。
上で説明したのはすべて関数呼び出しです。オブジェクト メソッドとして呼び出すなど、関数を呼び出す別の方法もあります。function f(x) { if (x === 1) { return 1; } else { return x * f(x - 1); } } var fn = f; f = function () {}; // may been warning by browser fn(5); // NaN
匿名関数を宣言し、それをオブジェクトの属性 (fac) に割り当てます。
ここで再帰を書きたい場合は、プロパティ自体を参照する必要があります:var obj1 = { num : 5, fac : function (x) { // function body } };
もちろん、関数呼び出しメソッドと同じ問題も発生します:
var obj1 = { num : 5, fac : function (x) { if (x === 1) { return 1; } else { return x * obj1.fac(x - 1); } } };
メソッド obj2のfac属性に代入した後もobj1.facを内部で参照する必要があるため…失敗しました。
別の方法が改善されます:var obj2 = {fac: obj1.fac}; obj1 = {}; obj2.fac(5); // Sadness
このキーワードを通じて関数が実行されるときにコンテキスト内の属性を取得します。これにより、obj2.fac が実行されるときに、obj2 の fac 属性が内部で参照されます。関数 。
しかし、この関数はコンテキストを任意に変更することによって呼び出すこともできます。つまり、ユニバーサル呼び出しと適用です:var obj1 = { num : 5, fac : function (x) { if (x === 1) { return 1; } else { return x * this.fac(x - 1); } } }; var obj2 = {fac: obj1.fac}; obj1 = {}; obj2.fac(5); // ok
したがって、再帰関数は再び適切に動作できなくなります。
この問題を解決してみるべきです。前に述べた関数の宣言方法を覚えていますか?var a = function b(){};
这种声明方式叫做内联函数(inline function),虽然在函数外没有声明变量b,但是在函数内部,是可以使用b()来调用自己的,于是
var fn = function f(x) { // try if you write "var f = 0;" here if (x === 1) { return 1; } else { return x * f(x - 1); } }; var fn2 = fn; fn = null; fn2(5); // OK
// here show the difference between "var f = function f() {}" and "function f() {}" var f = function f(x) { if (x === 1) { return 1; } else { return x * f(x - 1); } }; var fn2 = f; f = null; fn2(5); // OK
var obj1 = { num : 5, fac : function f(x) { if (x === 1) { return 1; } else { return x * f(x - 1); } } }; var obj2 = {fac: obj1.fac}; obj1 = {}; obj2.fac(5); // ok var obj3 = {}; obj1.fac.call(obj3, 5); // ok
就这样,我们有了一个可以在内部使用的名字,而不用担心递归函数被赋值给谁以及以何种方式被调用。
Javascript函数内部的arguments对象,有一个callee属性,指向的是函数本身。因此也可以使用arguments.callee在内部调用函数:
function f(x) { if (x === 1) { return 1; } else { return x * arguments.callee(x - 1); } }
但arguments.callee是一个已经准备被弃用的属性,很可能会在未来的ECMAscript版本中消失,在ECMAscript 5中"use strict"时,不能使用arguments.callee。
最后一个建议是:如果要声明一个递归函数,请慎用new Function这种方式,Function构造函数创建的函数在每次被调用时,都会重新编译出一个函数,递归调用会引发性能问题——你会发现你的内存很快就被耗光了。