フロントエンドの学習の過程では、必然的に多くの問題に遭遇します。そこで、今日は初心者の観点から 2 つの質問について説明します。
クロージャとは何ですか?
クロージャの機能は何ですか?
実際、JavaScript を学習するとクロージャがどこにでも出てきますが、それを認識して受け入れることができれば十分です。クロージャは、使用する新しい構文やパターンを学習する必要があるツールではありません。クロージャは、字句スコープに基づいてコードを作成することの自然な結果です。コードを記述するときに意図的にクロージャを作成する必要はほとんどありません。
この時点ですでに多くの友人が心の中でつぶやいていると思います、この語彙範囲は何ですか? パニックにならないで、ゆっくり聞いてください。 つまり、語彙範囲は語彙段階で定義される範囲です。つまり、字句スコープは、コードを記述するときに変数とブロックレベルのスコープを配置する場所によって決定されるため、字句アナライザーがコードを処理するとき(ほとんどの場合)、スコープは変更されません。 ——「あなたの知らない JavaScript」
まず例を見てみましょう:
function test(){ var arr = [] for(var i=0;i<10;i++){ arr[i]=関数(){ コンソール.ログ(i); } } 返却先 } var myArr = テスト() // myArr[0]() // myArr[1]() // ... for(var j = 0; j < 10; j++){ myArr[j]() } //退屈を避けるために、ここでは 2 番目のループを使用して、テスト関数の最初のループの関数を呼び出し、10 個の結果を出力します。
まずこのコードを分析してみましょう。常識によれば、このコードが実行されると、分析する必要があります。0 から 9 までの 10 個の数値が順番に出力されますが、for ループの実行には時間がかかりません (マイクロ秒単位で無視されます)。関数 test が arr を返すと、10 個の function(){console.log(i) が返されます。 );}、この時点では配列内の関数は実行されません。var myArr = test() が test 関数を呼び出すとき、for ループの実行時間は無視されるため、この時点では i はすでに 10 です。印刷されたものは 10 点中 10 点です。
この時点で、これがこれから説明するクロージャと何の関係があるのかと尋ねる人がいると思います。では、このコードを少し変更してアキュムレータに変更すると、どのように実装できるのでしょうか?
現時点では、「そんなことは簡単ではないか」と言う大物もいると思います。
var 定義を let 定義に変更して、最初の for ループがブロックレベルのスコープになり、その後アキュムレータになれるようにします。もちろん問題はありません
が、今日の話は ES5 にアキュムレーターを実装する方法です。次に、次のコードを見てみましょう:
function test(){ var arr = [] for(var i=0;i<10;i++){ (関数(j){ arr[j]=関数(){ コンソール.ログ(j); } })(私) } 返却先 } var myArr = テスト() for(var j = 0; j < 10; j++){ myArr[j]()注意深い人なら間違いなく
、
これはループ内の関数本体を自己実行関数に変更するものであることがわかると思いますが、このときの出力結果は0から9までの10個の数値を順番に出力するもので、これにはクロージャPackageが含まれています。このコードの実行を開始すると、2 番目の for ループが 10 回呼び出されます。各自己実行関数が実行されると、その自己実行関数の AO オブジェクトが作成されます。属性名は j ですが、通常、実行関数の実行後、その AO オブジェクトは破棄されますが、myarr[j] () が実行されると、スコープ チェーンの先頭にある arr[j] の AO オブジェクトが破棄されます。属性名 j を探しましたが、見つかりませんでした。スコープ チェーンを下方向に検索したところ、自己実行関数の AO オブジェクトで見つかりました。したがって、自己実行関数が終了すると、その AO が返されます。オブジェクトはガベージ コレクション メカニズムによってリサイクルされません。そうしないと、myarr[j] () の実行時にエラーが報告され、クロージャが形成されます。
関数 a(){ の
別の例を見てみましょう
。関数 b(){ var bbb = 234 コンソールログ(aaa); } 変数 aaa = 123 return b // bはaで生まれましたが、救われました} varglob = 100 var デモ = a()まず、プリコンパイルを使用して、
demo()のコードを分析します
。まず、グローバル GO オブジェクトを定義し、その変数宣言が GO の属性名として使用されます。は未定義です。関数宣言の場合、関数名が GO オブジェクトの属性名として使用され、値が関数本体に割り当てられます。この時点では、 GO{ glob: unknown--->100; demo: unknown; a: fa(){} }; となるはずです。次に、 function a {} }、最後に関数 a で関数 b をプリコンパイルして、b { b: unknown--->234}; の AO を作成します。このときのスコープ チェーンの順序は 1 です。関数 b の AO オブジェクト。 2. 関数 a の AO オブジェクト 3. グローバル GO オブジェクト。関数 b で aaa を出力するとき、関数 b の AO オブジェクトに aaa がない場合は、スコープ チェーンの先頭から開始して、第 2 レベルの関数 a の AO を見つけます。目的は、aaa の値を 123 として検索し、結果を出力することです。
プリコンパイルの観点から分析しなかった場合は、var Demon = a() が実行され、a 関数の実行が終了した時点で、aaa がエラーを報告するはずだと考えられます。常識的な分析によると、デモを実行すると、スコープ チェーンは b の AO オブジェクトと GO オブジェクトを作成するはずです。この時点では、b の AO オブジェクトのみが存在し、a の AO オブジェクトは存在しません。 aaa の値は出力されないはずですが、このとき aaa の値は 123 です。これは、a の AO オブジェクトが破棄されていないことを意味します。なぜでしょうか。その理由は、ここでクロージャが作成されるためです。 var Demon = a() の実行が完了すると、ガベージ コレクション メカニズムが「関数の実行が完了したと思います。実行中のメモリを解放してもらえますか」と尋ねます。 ? が、この時点では、関数 a は力なく首を振ることしかできず、「兄さん、実行した後、b を作成しましたが、b は私の制御下にないので、実行が完了したかどうかわかりません。」と言いました。 b. が呼び出されたかどうかわからないので、ガベージ コレクション メカニズムがそれを考えたのかどうかはわかりません。未完了のものはリサイクルしません。エラーを報告する必要があるため、現時点では AO オブジェクトはリサイクルされません。
これら 2 つの例を通して、クロージャについてはすでに理解できたと思います。次に、クロージャの機能について説明します。
クロージャの機能は、
- パブリック変数を実装することです。たとえば、アキュムレータ (3.js) を
- グローバル変数の汚染を防ぐための属性の
- キャッシュして、
- でき
- カプセル化、プライベート化、およびモジュール開発を実現
- ます
。 3.js)。
変数カウント = 0 関数 add() { 戻り数++ } console.log(add()); console.log(add()); console.log(add());
これは比較的一般的なアキュムレーション コードですが、インターンシップ中または職場でさえ、会社がアキュムレータをモジュール コードにカプセル化することを要求している場合は、この時点では、 module グローバル変数の定義をできるだけ避けようとしていますが、現時点ではグローバル変数を定義せずにそれを実現するにはどうすればよいでしょうか。
関数 add() { 変数カウント = 0 関数 a() { ++カウント console.log(カウント); } 返す } var res = add() レス() レス() // add 関数が終了した後、add の AO オブジェクトは破棄されません。これは、add 関数が実行された後、返された a は、呼び出されたかどうかを知らずにクロージャを形成するため、使用せずにモジュールにカプセル化できるためです。グローバル変数。
これらはクロージャとその機能に関する私の個人的な意見の一部です。現時点では、クロージャについては表面的な理解しかありません。その後、クロージャについての記事を作成する予定です。ご覧いただきありがとうございます。修正し、一緒に進歩してください。