ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptのクロージャ内のクロージャ

JavaScriptのクロージャ内のクロージャ

hzc
hzc転載
2020-06-16 09:30:452473ブラウズ

前面に書かれています


JavaScript ほとんど神話 JavaScript の使用経験はあるものの、クロージャの概念を実際には理解したことがない人にとって、クロージャを理解することは、ある種の生まれ変わりとなります。クロージャは、使用するために新しい構文を学習する必要があるツールではありません。クロージャは、字句スコープに基づいてコードを作成した場合の自然な結果です。言い換えれば、クロージャのためにクロージャを作成する必要はなく、作成するコードのどこにでもクロージャが存在します。 クロージャを本当に理解すると、ああ~、前に入力したコードにはたくさんのクロージャがあったことがわかります。

小さなデモ


次の例を注意深く見てみると、奇妙に感じるでしょう。これらはすべて result() を呼び出しているのに、なぜ結果が違う?

let count=500 //全局作用域
function foo1() {
  let count = 0;//函数全局作用域
  function foo2() {
    count++;//函数内部作用域
    console.log(count);
    return count;
  }
  return foo2;//返回函数
}
let result = foo1();
result();//结果为1
result();//结果为2

まず第一に、foo1() は foo2() 関数を返します。result() を呼び出すと、foo2() によって実行された関数が返されます。foo2() には何が含まれていますか? まず次のようになります。以下のように count 変数がありますが定義されていません JavaScript のスコープチェーンの定義により、関数内の変数が定義されていない場合はバブリング方式で上位を検索します次のレベルには続行しません。最上位ウィンドウまで 1 レベルを検索します。何もない場合は、未定義のエラーが報告されます。ここでは、foo1() で count を見つけるので、count 1、最初の出力は 1、そこにあります問題ありません。

 function foo2() {
    count++;
    console.log(count);
    return count;
  }

しかし、2 回目に result() を実行したときに問題が発生しました。なぜ 2 なのでしょうか? 処理によると、まず foo2() 関数内の count を探します。カウントがない場合は、外側の層でそれを探します。カウント = 0 が見つかった場合、カウント 1 は 1 である必要があります。これには、クロージャーの問題が含まれます。

JavaScriptのクロージャ内のクロージャ

最初デバッガを追加し、Google Chrome を右クリックしてリソースをクリックすると、右側にクロージャが表示されます。ブラウザの視覚化により、これが実際にクロージャであることが確認されました。そして、count=1 がクロージャに保存されています。これは、count を意味します=1 は破棄されておらず、次回 result() が呼び出されるときに count=2 になります。

スコープの理解


Clussure を学ぶには、これを理解する必要があります。 JavaScript のスコープ関連の知識:

1. グローバル スコープ
2. 関数スコープ
4. ブロックレベル スコープ (es6 の新機能、var 問題を解決し、 let 、 const を追加)

  var count = 100; //全局作用域
  function foo1() {
    var count = 0; //函数全局作用域
    return count; //返回函数
  }
  if (count == 1) {
    //块级作用域
    console.log(count);
  }

上記のコードは、スコープの分類を簡単に示しています。関数 (関数) もブロックレベルのスコープであることに注意してください。簡単に言うと、一般に {} をブロックとみなすことができます。レベル スコープ.

スコープ チェーン


スコープはスコープ内でネストされ、スコープ チェーンを形成します。外部スコープは内部スコープ スコープにアクセスできません。次の例を参照してください

function foo(){
var n=1
function foo2(){
  var m=1
  console.log(n) //1
}
foo2()
}
foo()
console.log(n) //err: n is not defined

上記のコードでは、内部 n にはグローバルにアクセスできませんが、ネストされた内部 foo2() は外部関数にアクセスできます。これは、スコープによって生成される特別な効果です。

さて、スコープ チェーンについては理解しました。例を見てみましょう (非常にわかりにくいので、注意して見てください):

 var name = 'Mike'; //第一次定义name
  function showName() {
    console.log(name);  //输出 Mike 还是 Jay ?     
  }

  function changeName() {
    var name = 'Jay'; //重新定义name
    showName(); //调用showName()
  }
  changeName();

上記の例の出力は何だと思いますか? 答えはマイクです。ここで新しい概念を紹介します。字句スコープには 2 つのモデルがあります:

  • 字句スコープ (静的): js 検索は、呼び出し時の位置ではなく、コードが記述されたときの位置に従って決定されます

  • 動的スコープ: Perl と Bash が現在使用されています (これについては自分で学習できます)

JavaScriptのクロージャ内のクロージャ

私たち字句スコープのルールを通じて再度分析できます。

  1. changeName() を呼び出すときは、この関数を見つけます

  2. var name = "Jay" を定義します

  3. showName()を呼び出します

  4. changeName()にshowName()があるか確認します。このメソッドは見つからなかったので、外側のメソッドを検索しました。レイヤーを検索し、

  5. を見つけました。console.log(name) を呼び出し、関数内を検索して名前があるかどうかを確認します。名前がない場合は、外側に検索して見つけました。name="Mike 「

  6. アウトプットマイク

クロージャ


理解しました 上記の知識を経て、私はついにクロージャに到達しました

クロージャの公式説明は 2 冊の本で説明されています:

##1. 小さな「黄色の」本 (JavaScript を知らない人): クロージャは次の場合に生成されます。関数は、現在の字句スコープ外で実行された場合でも、その関数が含まれている字句スコープを記憶してアクセスできます。

2. Little Red Book (JavaScript Advanced Programming) : クロージャとは、別の字句スコープにアクセスできることを意味します。 関数スコープ内の変数の関数

は非常に抽象的な概念です。私自身の理解の 1 つは次のとおりです。

変数 (上記の名前など) が関数に対してローカルではない場合、変数は関数のパラメータではありません。スコープと比較すると、これは自由変数 (外部変数を参照) であり、クロージャを形成します。
何と言うか? 最初に使用するものを見てみましょうデモ###

let count = 500; //全局作用域
function foo1() {
  let count = 0; //函数全局作用域
  function foo2() {
    let count2 = 1; //随便新增一个变量
    // count++;  注释
    debugger;
    //console.log(count); 注释
    //return count;  注释
  }
  return foo2; //返回函数
}
let result = foo1();
result(); //结果为1
result(); //结果为2

再次使用浏览器看看,这时我们就发现Closure已经消失了,这也就证实我说的,如果函数内部不调用外部的变量,就不会形成闭包.但是如果调用了外部变量,那么就会形成闭包. 这也就是说不是所有的函数嵌套函数都能形成闭包

<img src="https://img.php.cn/upload/image/731/425/784/1592270826700856.jpg" title="1592270826700856.jpg" alt="JavaScriptのクロージャ内のクロージャ">

最后我们再来看一个循环闭包的例子

for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    debugger;
    console.log(i); // 输出什么?   
  }, 1000);
}

答案 6 6 6 6 6 .因为setTimeout里面的回调函数是一个异步的过程(异步代表可以不用等待我这个代码先执行完,可以先往后执行),而for循环是同步的(代码只能从上往下的执行),立即执行,异步的setTimeout必须等待一秒才能执行,这时i早已经循环结束了.
解决办法有三个:

  1. 将for循环中的var 改成let

for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    debugger;
    console.log(i); // 1 2 3 4 5 
  }, 1000);
}

这样就没有问题了, 因为let是有块级的功能,每一层循环都是独立的,互不影响,所以才能正常输出.
      2. 把setTimeout()套上一个function

for (var i = 1; i <= 5; i++) {
  log(i); // 1 2 3 4 5
}
function log(i) {
  setTimeout(function timer() {
    debugger;
    console.log(i);
  }, 1000);
}

这样同样能够实现这个功能,原理和第一个方法一样,每一个log()都是独立的,互不影响,这样才能有正确的结果,var就是因为没有块级的功能,才会出问题 3. 包装成匿名函数

for (var i = 1; i <= 5; i++) {
  (function (i) {
    setTimeout(function timer() {
      debugger;
      console.log(i);
    }, 1000);
  })(i)
}

前面一个(func..)定义函数,后面一个(i)调用,这再JavaScript叫做立即执行函数,其实与第二种方式是一样的,只是写法不一样.

结语


理解JavaScript闭包是一项重要的技能,在面试中也常常会有,这是迈进高级JavaScript工程师的必经之路.

推荐教程: 《js教程

以上がJavaScriptのクロージャ内のクロージャの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。