2017-09-22 1 views
0

This linkには、JavaScriptのクロージャに関する例があります。例5ではこの例のどの部分がクロージャですか?

、我々はこのコードを持っている:問題の説明では

function buildList(list) { 
    var result = []; 
    for (var i = 0; i < list.length; i++) { 
    var item = 'item' + i; 
    result.push(function() {console.log(item + ' ' + list[i])}); 
    } 
    return result; 
} 

function testList() { 
    var fnlist = buildList([1,2,3]); 
    // Using j only to help prevent confusion -- could use I. 
    for (var j = 0; j < fnlist.length; j++) { 
    fnlist[j](); 
    } 
} 

testList(); //logs "item 2 is undefined" 3 times 

を、それが 『未定義ITEM2「あなたが例を実行すると、ことに注意してください』記載されている3回警告さです!前の例と同様に、buildListのローカル変数にはクロージャが1つしかありません。

上記のコードのどの部分がクロージャですか?私は、どの部分が閉じられているのか分からないので、例と説明で捨てられているので、コードがなぜ定義されていないのか理解できません。

+0

クロージャは、スコープの一部である 'function(){console.log(item + '' + list [i])}'関数と 'item'、' list'および 'i'変数です。その関数のスコープの一部ではなく、その関数が含まれています。重要なのは、すべての関数において、 'item'、' list'、および 'i'は常に同じ変数のセットです。 – Ryan

答えて

2

この例では、list,itemおよびiが閉じられています。ファンクションがresultにプッシュされているうちに3つのファンクションが実際にと呼ばれるまで評価されないため、問題が発生しています。

varを使用して1つだけi、及び上閉じますbuildList関数の1つのitem変数、result配列の点で非常にすべての機能があるので、これは、実際ES6は同様letキーワードを追加した理由の良い例であります同一のiと同じitemの変数に変換されます。もちろん、各反復で変更されます。 の最後の値を設定しようとしています。これはlist.lengthです。これは明らかにリストの最後の要素のインデックスより1だけ大きいので、list[i]にアクセスしようとすると、戻り値は未定義です。

forループでvar i = ...let i = ...を変更し、var item = ...let item = ...がこれを修正し、期待通りの結果を返します。

function buildList(list) { 
 
    var result = []; 
 
    for (let i = 0; i < list.length; i++) { 
 
    let item = 'item' + i; 
 
    // close on i and list. i does not get evaluated until fn gets called, 
 
    // so we need to create a new i for each iteration of the loop, hence the use of let 
 
    result.push(function() {console.log(item + ' ' + list[i])}); 
 
    } 
 
    return result; 
 
} 
 

 
function testList() { 
 
    var fnlist = buildList([1,2,3]); 
 
    // Using j only to help prevent confusion -- could use I. 
 
    for (var j = 0; j < fnlist.length; j++) { 
 
    fnlist[j](); 
 
    } 
 
} 
 

 
testList();

ES6 letを使用せずにこれを行う方法はこれのように、手動でループの反復ごとにilistに評価し、クローズする関数を作成することです。

FYI:ベストプラクティスの観点から見ると、これは実際にループの内部で関数を定義しないようにする適切な方法です。

function logFunction (index, list) { 
 
    // inside here, index and list get evaluated as their current values in the loop 
 
    // and then closed on for that specific value in the function being returned 
 
    return function() { 
 
    console.log("item" + index + " " + list[index]); 
 
    } 
 
} 
 
function buildList(list) { 
 
    var result = []; 
 
    for (var i = 0; i < list.length; i++) { 
 
    // pass i and list to function to manually create closure 
 
    result.push(logFunction(i, list)); 
 
    } 
 
    return result; 
 
} 
 

 
function testList() { 
 
    var fnlist = buildList([1,2,3]); 
 
    // Using j only to help prevent confusion -- could use I. 
 
    for (var j = 0; j < fnlist.length; j++) { 
 
    fnlist[j](); 
 
    } 
 
} 
 

 
testList(); //logs "item 2 is undefined" 3 times

+0

私は今エクササイズのスコープ部分全体を理解しています。なぜインデックスとリストをlogFunction()に渡すのですか?ここで私を楽しませるなら、どの部分がクロージャですか?そのコード(2番目のスニペット)では、私は4-6行を閉じています。それはどこでも使用できる永続的な値を返すので、クロージャとみなされ、スコープのレベルに関係なく変更する能力を取り除きます。あれは正しいですか? – bpoinder85

+0

@ bpoinder85正確ではありません。クロージャーは、値の変更可能性ではなく、コンテキストを参照します。クロージャを作成すると、実行時に語彙的に範囲外になっていても、変数を特定のスコープに**囲みます。 – mhodges

+0

@ bpoinder85 'logFunction'は、返される関数がそのレキシカルスコープの外にある変数を必要としていることを知っているということです。その関数を(クロージャなしで)実行すると 'logFunction'のローカル変数なので' index'や 'list'については何も知らないでしょう。 'logFunction'が返ってくると、それらのローカル変数はなくなります。しかし、返される無名関数によって必要とされるので、それらは、閉包の定義である変数を保持します。変数は、それらが字句的に定義されたコンテキスト外にあります。 – mhodges

1

クロージャは:

function() {console.log(item + ' ' + list[i])} 
アレイが構築される

、通過iクロージャ関数スコープのi可変外部への参照です。 i変数は常に同じで、forループのためにインクリメントしています。したがって、各クロージャ関数を呼び出すとlist[list.length]にアクセスしようとします(iは0からlist.length-1までのループ内で2になるため)したがってundefined例外があります。

+0

あなたは私がいつも同じで、増分しているということを明確にしてもらえますか?これはお互いに奇妙です。私が2になった場合、最終結果は「Item 2 3」だけではないのはなぜですか? – bpoinder85

1

これはクロージャのようになります。それは親スコープからiや他の変数を使用しているためfunction() {console.log(item + ' ' + list[i])}

コードを実行するとどうなりますか?

ステップ1.プッシュ最初の関数、親スコープへのアクセスを持っている - 私は、ステップ0

===:2プッシュ第二の機能 - 同じことは、 - >私は(これは、同じ1 ===ステップ1のようi - 今の両方の機能がi === 1

を持っ...

工程:(list.length-1)配列内list.length - 1機能をプッシュ - >今i === list.length-1(これはすべてに同じであるiクロージャにはアクセスできます。

最後のステップは、fori++です。

今あなたが作成した関数を呼び出しを開始それらはすべて今list.lengthに等しく、それがアクセスしたとき、それはundefinedですのでlist[list.length]それが配列の範囲外だと同じiへの参照を持っています。

関連する問題