ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のクロージャをよく見てみましょう。

JavaScript のクロージャをよく見てみましょう。

coldplay.xixi
coldplay.xixi転載
2020-10-21 17:41:221632ブラウズ

TodayJavaScript 列ではクロージャを紹介します。

JavaScript のクロージャをよく見てみましょう。

まえがき

JavaScript を詳しく学びたい場合、クロージャ の概念はほぼ避けられません。 クロージャとは何かをよく見てください。
初心者の方は、この記事の内容をよりよく理解するために、まず前の記事を読んでください:

  • 【JavaScript】興味深いスコープとプロモーション

クロージャを理解する

1. クロージャとは

まず MDN におけるクロージャの定義を見てみましょう:

A関数はその周囲の状態 (字句環境、字句環境) への参照とバンドルされています (または関数が参照で囲まれています)。この組み合わせはクロージャです。

クロージャの定義は非常に曖昧です。一般的な方法で説明すると、次のようになります。

  • 関数内で定義された関数とそのスコープの組み合わせは、 クロージャ と呼ばれます。

これはまだ非常に抽象的かもしれませんが、最初に概念があり、この概念については以下で詳しく説明します。クロージャについては、実際には別の説明があります:

  • 内部関数は関数内で宣言され、返されます。外部はこの内部関数を通じて内部値にアクセスできます。そのような関数を呼び出します。 it Closure

最初の説明とは異なり、2 番目の説明では関数自体をクロージャと呼んでいます。この説明も合理的ですが、より正確には、実際にこの関数を呼び出すことができます クロージャ関数。この場合、実際には両方の解釈に問題はなく、同時に存在することができます。

2. クロージャを感じる

クロージャの概念を読んだ後、実際の例を通してクロージャを感じてみましょう。閉鎖が発生する条件は何ですか?

クロージャとはどのようなものですか?

function outer(){    var a=0;    function inner(){        console.log(++a);
    }    return inner;
}var f=outer();
f();  //1f();  //2复制代码

まず、この例でクロージャがどのように反映されるかを見てみましょう。まず outer 関数を定義し、次に outer 関数内で inner 関数を宣言してそれを返します。

外部では、outer 関数を実行し、返された inner 関数を f に保存します。これで fこれが私たちの作業ですクロージャ関数を呼び出します。

この例では、f 関数を 2 回実行し、それぞれ 12 を出力し、f# # を実行していることを示しています。関数内の ##console.log(a); ステートメント。スコープに関する前述の説明によれば、変数は関数が配置されているスコープ チェーン内で見つかり、最後に outerこの変数は関数内で見つかり、値が追加されました。 同時に、出力した結果が

1

であることは 2 回ではないことに注意してください。これは、スコープが共有されており、outer# のスコープと見なすことができることを示しています。 ## は外側に拡張されました。 outer

スコープは本来実行後に解放されるべきですが、

GC メカニズム (ここでは紹介しません) によれば、次のようになります。

    outer
  • をリリースする準備ができていますが、a 変数が参照されているようで、外部にも保存されています。いつ呼び出せばよいかわかりませんそれ?わかりました、それならまだリリースしません。 クロージャはこのようにして誕生しました。
  • スコープが消えた後も、スコープ内の変数には外部からアクセスできます

クロージャ生成の条件は?

これまでに示した例と説明に基づいて、クロージャを生成するために必要な条件をまとめてみましょう。

外部関数で内部関数を宣言し、内部関数を返します。関数。
  1. ここで、outer は外部関数、
    inner は返される内部関数です。 内部関数は外部関数の変数を参照します。
  2. この例では、inner 関数は変数
    a を参照します。 外部関数を実行し、クロージャを作成し、返された内部関数を保存する必要があります。
  3. 外部関数outerを実行し、返された内部関数を保存する必要があります。この例では、
    var f=outer()です。 これら 3 つの条件が同時に満たされた場合にのみ、クロージャを生成できます。実現するのは難しいように思えますが、実際には、実際のコード内でクロージャが誤って生成されることがよくあります。
今の紹介で、誰もがクロージャの概念を理解する必要があると思いますが、次にクロージャのあらゆる側面を深く理解することにしましょう。

クロージャを詳しく体験する

1. 複数のクロージャを作成する

先ほどの例では、クロージャを作成し、クロージャ関数を複数回実行した後、すべての変数を追加しました。

a

が同じスコープ内にある場合、複数のクロージャを作成しようとするとどうなりますか:

function outer() {  var a = 0;  function inner() {    console.log(++a);
  }  return inner;
}var f1 = outer();var f2 = outer();
f1();  //1f2();  //1复制代码

这段代码在刚刚的例子上进行了改动,我们执行了两次外部函数outer,并分别用不同的变量f1f2保存。当执行f1f2时会发现,输出的结果都是1,说明f1f2的作用域是独立的,f1f2属于两个不同的闭包,我们用一张图来理解下:JavaScript のクロージャをよく見てみましょう。

当分别创建f1f2时,调用了两次outer函数,创建了两个不同的上下文。而当f1f2分别执行时,根据作用域的查找规则,会去对应的作用域中查找变量,并执行增值输出,所以最终两个值均为2

2.使用闭包实现模块化

我们知道,作用域的外部无法拿到作用域内部的值,而通过闭包,我们可以把作用域我们需要的值或者方法暴露出去,比如:

function outer() {  var myNumber = 0;  function addNumber() {
    myNumber++;
  }  function getNumber() {    return myNumber;
  }  return {
    addNumber, getNumber
  }
}var module = outer();module.addNumber();module.addNumber();module.getNumber();复制代码

在这个例子中,我们同样定义了一个外部函数outer,另外还分别定义了两个函数addNumbergetNumber,用于增加和获取变量myNumber

当我们执行outer()语句的时候,会创建一个上下文,同时把内部返回的对象保存在module变量上,此时我们就创建了一个闭包,以及一个包含方法addNumbergetNumber的对象。

由于外部是无法直接访问变量myNumber的,但由于闭包的原因,addNumbergetNumber是可以访问到这个变量的,因此我们成功的把变量myNumber隐藏了起来,并且对外只提供了增加和获取myNumber值的方法。

试着用闭包解决问题

通过刚刚的例子,相信大家应该对闭包有了一定了解,接下来我们试着运用闭包来解决实际问题,先看一下例子:

for (var i = 0; i < 2; i++) {      setTimeout(() => {        console.log(i);
      }, 500);
    }复制代码

这是一个十分容易令人误解的例子。接触过的小伙伴肯定都知道,最后会输出两次2而不是依次输出01,我们来看看为什么会这样。
首先,外部是一个for循环,执行了两次语句。

for (var i = 0; i < 2; i++) {
    ... //  执行两次}复制代码

在函数的内部,我们调用了setTimeout函数,关键的地方来了,这个函数是一个异步函数,并不会马上执行,所以实际上等外部的for循环执行结束了,才会真的执行setTimeout中的函数。还有第二个关键点在于,for循环中,var定义的变量相当于定义在全局,而不存在块级作用域。那么刚刚的代码就可以近似的看成这样了。

var i=0;
i++;    //i=1i++;    //i=2console.log(i);console.log(i);复制代码

非常直接粗暴,但可以很清晰的看出输出结果为何是两次2了,因为大家共用了同一个作用域,i的值被覆盖了。那么知道了问题出在哪里,我们试着用上我们刚刚学习的闭包,来创建不同的作用域:

   for (var i = 0; i < 2; i++) {      function outer() {        var j = i;        setTimeout(function inner() {          console.log(j);
        }, 500);
      }
      outer();
    }复制代码

我们按照闭包的样式对刚刚的代码进行了改造,这里的setTimeout并不直接就是inner函数,这是因为它在这里起到了定义inner函数,并保存执行inner函数的功能。

我们可以看到,最终结果依次输出了01,说明我们的闭包是成功了的,但这样的闭包比较臃肿,我们试着提高一下,写的更加优雅一点:

   for (var i = 0; i < 2; i++) {
        (function() {   //用自执行函数代替了`outer`函数的定义和执行两个步骤
            var j = i;            setTimeout(function inner() {                console.log(j);
            }, 500);
        })();
    }复制代码

还可以再简化一下:

for (var i = 0; i < 5; i++) {        for (var i = 0; i < 2; i++) {
            (function(j) {//用自执行函数代替了`outer`函数的定义和执行两个步骤
                setTimeout(function inner() {                    console.log(j);
                }, 500);
            })(i);//var j=i的步骤,通过传入i值替换
        }
    }复制代码

这样就大功告成了!

总结

本篇首先介绍了闭包的定义以及不同人对闭包的理解,之后介绍了闭包产生的原因并总结了三个条件,之后举例说明了如何创建多个闭包和通过闭包实现模块,最后讲述了如何通过闭包解决for循环中使用异步函数依次输出值的问题。其实闭包没有想象的那么可怕,只要你愿意静下心去探索去了解,闭包也会对你敞开大门~

写在最后

都看到这里了,如果觉得对你有帮助的话不妨点个赞支持一下呗~

以后会陆续更新更多文章和知识点,感兴趣的话可以关注一波~

如果哪里有错误的地方或者描述不准确的地方,也欢迎大家指出交流~

相关免费学习推荐:javascript(视频)

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

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