12

私は、 Javascriptを理解し、 Javascript: TheGoodPartsをもう一度読むことを試みることに一歩前進することにしました。そして、ここに最初の疑問があります:

グローバル変数は悪であるため、使用を避けたいとしましょう。そのため、次のようになります。

var digit_name = function(n) {
 var names = ['zero','one','two','three'];
 return names[n];
}

D.Crockfordは、関数が呼び出されるたびにの新しいインスタンス化が行われるため、これは遅いと主張していnamesます。それで、彼はこれを行うことによってクロージャーソリューションに移動します:

var digit_name = function () {
  var names = ['zero', 'one', 'two', 'three'];
  return function (n) {
    return names[n];
  }
}();

これにより、names変数がメモリに格納されるため、を呼び出すたびにインスタンス化されるわけではありませんdigit_name

理由を知りたいですか?と呼ぶときdigit_name、なぜ最初の行が「無視される」のですか?私は何が欠けていますか?ここで実際に何が起こっているのですか?

私はこの例を本だけでなく、このビデオにも基づいています(26分)

(誰かがより良いタイトルを考えている場合は、必要に応じて提案してください...)

4

3 に答える 3

12

次のように、2 番目のサンプル関数を即時実行 (つまり、自己呼び出し) 関数にするつもりだったと確信しています。

var digit_name = (function () {
  var names = ['zero', 'one', 'two', 'three'];
  return function (n) {
    return names[n];
  }
})();

この違いには、クロージャーを使用したスコープ チェーンが含まれます。JavaScript の関数には、関数自体の中で宣言されていない変数の親関数を検索するという点でスコープがあります。

JavaScript の関数内で関数を宣言すると、クロージャーが作成されます。クロージャーは、スコープのレベルを表します。

2 番目の例でdigit_nameは、自己呼び出し関数と等しく設定されています。その自己呼び出し関数はnames配列を宣言し、無名関数を返します。

digit_nameしたがって、次のようになります。

function (n) {
  //'names' is available inside this function because 'names' is 
  //declared outside of this function, one level up the scope chain
  return names[n];
}

names元の例から、返された無名関数 (現在は ) からスコープ チェーンの 1 つ上のレベルに宣言されていることがわかりますdigit_name。その無名関数が を必要namesとする場合、宣言された変数が見つかるまで、スコープ チェーンを上に移動します。この場合、namesスコープ チェーンの 1 レベル上の変数が見つかります。

効率について:

names2 番目の例は、自己呼び出し関数が起動したとき (つまり、 var digit_name = (function() { ... })(); ) に一度だけ宣言されるため、より効率的です。がdigit_names呼び出されると、 が見つかるまでスコープ チェーンを検索しますnames

最初の例では、呼び出されるnamesたびに gets が宣言されるdigit_namesため、効率が低下します。

グラフィカルな例:

Douglas Crockford から提供された例は、クロージャとスコープ チェーンがどのように機能するかを学習するときに、かなり難しい例です。多くのものが小さなコードに詰め込まれています。次のようなクロージャーの視覚的な説明をご覧になることをお勧めします。 Context.htm

于 2012-08-09T06:06:37.823 に答える
3

これは答えではありませんが、与えられた例がまだ混乱しているように見える場合の説明です。

まず、明確にしましょう。digit_nameコードで最初に表示される関数ではありません。その関数は、別の関数を返すために作成されたものです (そうです、数値、文字列、またはオブジェクトを返すことができるのと同じように、関数を返すことができます。実際、関数はオブジェクトです)。

var digit_name = (
    function () { // <------------------- digit name is not this function

        var names = ['zero', 'one', 'two', 'three'];

        return function (n) { // <------- digit name is really this function
            return names[n];
        }
    }
)();

例を単純化し、クロージャーの考え方だけを説明するために、自己呼び出し関数 (まだ慣れていないかもしれません) などと混同するのではなく、次のようにコードを書き直すことができます。

function digit_name_maker () {
    var names = ['zero', 'one', 'two', 'three'];

    return function (n) {
        return names[n];
    }
}

var digit_name = digit_name_maker(); // digit_name is now a function

注意すべきことは、names配列が関数で定義されていても、digit_name_maker関数で引き続き使用できることですdigit_name。基本的に、両方の関数がこの配列を共有します。それが基本的にクロージャーとは何か、つまり関数間で共有される変数です。私はそれを一種のプライベートグローバル変数と考えるのが好きです。すべての関数がアクセスを共有しているという点でグローバルのように感じますが、クロージャーの外側のコードはそれを見ることができません。

于 2012-08-09T06:33:29.950 に答える
0

簡単に言えば、最初のコードの問題は、呼び出しごとに配列を作成し、そこから値を返すことです。を呼び出すたびに配列を作成しているため、オーバーヘッドが発生します。

2 番目のコードでは、1 つの配列のみを宣言するクロージャーを作成し、その配列から値を返す関数を返します。基本的に、digit_nameすべての呼び出しを行うのではなく、独自の配列を運ぶようになりました。関数は、クロージャの既存の配列から取得します。


一方、クロージャーは、適切に使用しないと、メモリを消費する可能性があります。クロージャーは通常、内部コードを外部スコープから保護するために使用され、通常、外部からのアクセスを制限して実装されます。

オブジェクトへのすべての参照が「null」でない限り、オブジェクトは GC によって破棄されません。クロージャーの場合、それらの内部参照を強制終了することができない場合、オブジェクトは GC によって破棄されず、永久にメモリを消費します。

于 2012-08-09T06:17:24.480 に答える