2

私はこの閉鎖のことに関していくつかの混乱を抱えています。以下に2つの別々のコードがありますが、それらは似ていますが、出力が異なります。

function setup(x) {
var array = [];
for(var i=0;i<arguments.length;i++){
    array[i]= arguments[i];
}
return array;
}
console.log(setup('a','b'));  // will output ["a","b"] 

--------------
function f() {
var i, array = [];
for(i = 0; i < 3; i++) {
    array[i] = function(){
        return i;
    }
}
return array;
}

var a = f();                 
console.log(a());       //output: [function(),function(),function()]
console.log(a[0]());    //output: 3 //same output in a[1]() and a[2]() calls as well

さて、私の質問は、どうして出力が違うのかということです。上記の両方のコードは配列を返します。最初のコードでは、配列内のすべての要素を正しく出力しますが、2番目のコードでは、なぜ[1,2,3]を出力しないのですか?

4

2 に答える 2

4

2 番目の例では3、ループ内で関数を作成していますが、すべての関数が同じ変数スコープで作成されているため、すべて同じi変数の値を参照して返します。

したがって、i関数から返される値は、関数が呼び出された時点の値を表します。ループのにそれらを呼び出しているため、の値はiis3であり、それが返される値です。

これが閉鎖の意味です。関数は、変数が作成された変数スコープに存在する変数を「閉じます」。変数の値を閉じるのではなく、変数自体を閉じるので、常に変数の現在の状態を取得します。

各関数が の異なる値を参照するにはi、各関数を独自の一意の を持つ個別の変数スコープで作成する必要がありますi

JavaScript で新しい変数スコープを作成する唯一の方法は関数を呼び出すことであるため、新しい関数呼び出し内で各関数を作成する必要があります。

function makeFunction(j) {
    return function(){
        return j;
    };
}

function f() {
    var i, array = [];
    for(i = 0; i < 3; i++) {
        array[i] = makeFunction(i);
    }
    return array;
}

そこで、 という新しい関数を作成しましたmakeFunction。単一のパラメーターを受け取り、そのパラメーターを参照して返す新しい関数を返します。

の呼び出しごとmakeFunctionに新しい一意の変数スコープが作成されるため、返される各関数は独自の一意の変数を参照するため、呼び出されたときに存在していたjの値を返します (関数が を変更しない限り、必要に応じて実行できます)jmakeFunctionj

jわかりやすくするために変数名を使用したことに注意してください。i同様に、または他の名前を使用できます。

于 2012-11-03T00:43:02.673 に答える
0

JavaScript には、関数ステートメントと関数があります。最初は名前付き関数を宣言し、後者は名前付き関数または無名関数に評価されます。関数式を使用しています。

あなたがやりたいと思うのは呼び出し式です。を返す関数をすぐに呼び出すだけですi

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        // The extra parentheses around the function are unnecessary here.
        // But this is more idiomatic, as it shares syntax with how function
        // expressions are introduced in statements.
        // I.e. you could just copy-paste it anywhere.
        array[i] = (function () {
            return i;
        })(); // don't rely on automatic semicolon insertion
    }
    return array;
}

3また、問題のある例では、すべてのクロージャーが同じ変数をキャプチャするため、すべてのクロージャーが返されることに注意してくださいi

編集:前の段落をより明確にするために、本当に 3 つの異なるクロージャーが必要な場合は、それぞれに新しいスコープを作成する必要があります。他の言語とは異なり、Javascript はブロックを開くだけではスコープを作成しません。関数内にスコープを作成するだけです。これを行うには、次の 2 つの方法があります。

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        // With a parameter in an inner function
        array[i] = (function (n) {
            return function () {
                // A new n is captured every time
                return n;
            };
        })(i);
    }
    return array;
}

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        array[i] = (function () {
            // With a variable in an inner function
            var n = i;
            return function () {
                // A new n is captured every time
                return n;
            };
        })();
    }
    return array;
}

ただし、次の例は間違っています。なぜなら、がブロックn内で宣言されforていても、関数の先頭で宣言され、ブロック内でのみ初期化されているかのようになるからforです。これは JavaScript であり、Java ではなく、C ではなく、C# ではなく、<括弧で囲まれた言語> でもないことを忘れないでください。

function f() {
    var i, array = [];
    for (i = 0; i < 3; i++) {
        var n = i;
        array[i] = function () {
            return n;
        };
    }
    return array;
}

同等のコード:

function f() {
    var i, array = [];
    var n;
    for (i = 0; i < 3; i++) {
        n = i;
        array[i] = function () {
            return n;
        };
    }
    return array;
}
于 2012-11-03T00:50:40.027 に答える