JavaScript 概要共有の閉鎖

WBOY
リリース: 2022-11-07 16:31:08
転載
1306 人が閲覧しました

この記事では、JavaScript に関する関連知識を提供し、主にクロージャとは何か、クロージャがこのように設計されている理由、使用方法など、クロージャに関連する問題を紹介します。以下の関連コンテンツをご覧ください。皆様のお役に立てれば幸いです。

JavaScript 概要共有の閉鎖

[関連する推奨事項: JavaScript ビデオ チュートリアル Web フロントエンド ]

クロージャとは?

知識ポイントについて、どこから始めても、この知識ポイントを真に理解するには、3 つの質問を徹底的に理解し、その後、実際に実践して理解できるようにする必要があると私は常々信じています。それを主張すること、マスターすること。これら 3 つの質問は次のとおりです:

  • とは何ですか?
  • なぜデザインするのか?
  • どこで使用できますか?

まず、クロージャとは何かという質問に答えてください。ほとんどの人は多くの関連記事を読んでいるはずですし、多くの人が独自の説明も行っているので、最初に私が理解している説明をします。 前提条件となる概念が 2 つあります。

  • クロージャは字句解析中に決定されるため、字句の範囲に関連します。

  • クロージャが存在するための前提条件は、プログラミング言語が第一級市民としての関数をサポートする必要があるため、関数に関連することになります。

最終的な結論は次のとおりです:

  • クロージャは最初に構造体であり、この構造体のコンポーネントは関数です。
  • クロージャは関数によって生成された構造 であり、関数は独自の字句スコープを宣言することを覚えています。
  • メモリ内では、関数が呼び出されると、関数が生成する関数実行コンテキスト内のスコープ チェーンは親の字句スコープを保存するため、親変数オブジェクトはその存在によって参照されません。破棄され、使用するためにメモリ内に常駐します。この状況を閉鎖と呼びます。
上記の説明は、すでにクロージャを理解している人にとってはわかりやすいはずですが、実際のところ、クロージャをまったく知らない人にとってはまったく理解できないかもしれません。さらに、実際にはこの定義を覚えているだけで、その意味をよく理解していない人も少なくありません。

そこで、クロージャとは何かを理解するために、必ずしも正確ではない例えを使いたいと思います。あなたが記事を書いてそれを自分のサーバーに置き、自分の記事のうち 3 つを参考文献として引用したと想像してください。したがって、現時点では、記事のサーバー環境はクロージャに似ています。

インターネット上で公開された後、他のプラットフォームに転載されます。他のプラットフォームの読者が記事をクリックして、引用した記事を読み続けたい場合は、正確にリダイレクトされます。記事に移動します。サーバー上で。

この例では、この記事は、この記事が書かれたサーバー環境への参照を保存します。したがって、記事のどこを読んでも、記事内で記憶されている参照記事参照は常にサーバー内のアドレスを指します。この状況は、クロージャ機能を使用して呼び出されます。

もしかしたら、この例はまだ理解しにくいかもしれません。結局のところ、あまり正確ではありません。クロージャの概念は少し抽象的で、実際に使用できる具体的な例が思いつきませんでした。比喩として。もっと適切な例えを持っている人がいたら、注釈を付けて説明します。

なぜクロージャを設計する必要があるのでしょうか?

これが設計されている理由について、私の表面的な理解は、

JavaScript が非同期シングルスレッド 言語であるためです。非同期プログラミングの最大の問題は、関数を作成するときに、実際に呼び出される時刻がいつになるかわからないことです。

これは、

メモリ管理にとって大きな問題です。通常、同期的に実行されるコードの場合、関数が宣言されて呼び出されたときに必要なデータはメモリ内に残ります。アクセス可能です。非同期コードは、関数のコンテキストが破棄された可能性があることを宣言することがよくありますが、関数が呼び出されるまでに、必要な外部データがメモリ内で消去されている場合、これは大きな問題になります。

したがって、JavaScript の解決策は、

関数が以前に取得できたデータの範囲を記憶できるようにし、それらはすべてメモリに保存されるようにすることです。関数が再利用されない限り、メモリ、メモリ自体、およびメモリが記憶できる内容 どのスコープも破壊されません

ここで記憶できるスコープとは、静的であるため記憶する必要がある字句スコープを指します。

これも JavaScript の静的設計範囲が原因で発生します。動的スコープの場合、関数が呼び出されるときは、呼び出されたときの環境のみが必要であり、独自のスコープを記憶する必要はありません。

要約すると:

  • クロージャは、非同期プログラミングにおける字句スコープ とデータ取得における不十分なメモリ管理 によって引き起こされる問題を 解決するために作成されます。
古典的な質問

もともと閉店の状況を最下層から説明しようと考えていたのですが、後でいろいろな記事をチェックしてみると、すでによく書かれている記事があることに気づきました。これが JavaScript クロージャの基本的な動作メカニズムです。最初にこの説明を読んでから、後で私が書く内容を読んでいただければと思います。

次のような非常に古典的な面接の質問から始まる記事はたくさんありますが、根本的なところからきちんと説明している人がいないようなので、全体のプロセスを整理して理解するつもりです。 。

for (var i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}
ログイン後にコピー

基本的に、基本的なスキルを持つすべての人は、出力が 3 つ 3 つであることが一目でわかります。

その後、順番に出力するように変更します。通常は、var を次のように変更するだけです:

for (let i = 0; i < 3; i++) {  setTimeout(function cb() {    console.log(i);
  }, 1000);
}
ログイン後にコピー

このようにすると、出力は 0、1、2 になります。毎回ではなく同時に 1 秒に 1 回出力します。

では、問題はなぜでしょうか?

私が書いたことと同じかどうかを確認するために以下を読まなくても、最初に自分の説明を書いても構いません。

1. まず、変数 i が var である状況について説明します。

コードの実行が開始されると、実行コンテキストのスタックとメモリの状況は次のようになります。 グローバル オブジェクトの

変数 i とグローバル実行コンテキストの変数環境の 変数 i は同じ変数です。

その後、ループが開始されます。i = 0 のとき、最初のタイマーがマクロ タスク キューにスローされます。マクロ タスクに関連するコンテンツは、イベント ループ カテゴリに属します。当面は、setTimeout がキューにスローされ、後で実行されることだけを理解してください。 このとき、そのコールバック関数 cb がヒープメモリ上に作成され、関数作成時に [[scope]] が作成されますが、実際の ECMA ルールでは、[[scope]] はその親スコープを指します。現在のグローバル オブジェクトである関数 (スコープは概念的なもので、メモリ内の実際の表現はデータを保存する構造であり、オブジェクトまたはその他の何かである可能性があります)。 ただし、V8 エンジンの実装では、実際にはグローバル オブジェクトを指すのではなく、親スコープ内のどの変数が関数によって使用されているかを分析し、これらの変数をクロージャに格納してからスコープを指します。各関数には Closure オブジェクトが 1 つだけあります。


Closure オブジェクトが Chrome で表示される場所に関する情報は次のとおりです。 ご覧のとおり、bar 関数の作成時には、親スコープの name 変数のみが参照されるため、変数名のみがクロージャ オブジェクトに格納され、変数 age は存在しません。


同様に、i = 1 と i = 2 は同じで、最終結果は次のようになります。

最後に、 i = 3 であるため、ループは終了し、グローバル コードが実行されます。このときの結果は次のようになります。

次に、タイマー コールバック関数の実行処理が開始されます。 最初のタイマーでコールバック関数の実行を開始し、それを実行コンテキスト スタックにプッシュし、出力 i を実行します。ただし、変数 i は字句環境と変数環境で見つからないため、独自の [[スコープ] に移動します。 i は Closure オブジェクトで見つかり、3 に等しく、出力結果は 3 になります。

同様に、次の 2 つのタイマーでもプロセスは同じであり、実際にはタイマーの開始時刻がループ内ですぐに実行されるため、実際には 3 つのタイマーが実行されます。各関数のタイミングは 1 秒間一貫しており、最終的な出力結果は 3 つの 3 がほぼ同時に出力されます。 1 秒間隔ごとに 3 を出力するのではなく、もちろんこれはタイマー関連の知識です。

#2. 次に、var を let

# に変更した後に実際に何が変わったのかを説明します。最初に作成されたときの状況は次のとおりです。

ループ本体に入った後、i = 0 の場合:

次に、i = 1 の場合の状況に入ります:

最後に i = 2 の状況に入ります。これは基本的に i = 1 と同様です。

最後に i は i value 3 になり、サイクルは終了します。タイマー作業の開始:

当执行第一个定时器的回调函数时,创建了函数执行上下文,此时执行输出语句i时,会先从自己的词法环境里寻找变量i的值,也就是在 record环境记录里搜索,但是不存在。因而通过自己外部环境引用outer找到原先创建的块级作用域里 i = 0的情况, 输出了i值为0的结果。

对于之后的定时器也都是一样的情况,原先的块级作用域由于被回调函数所引用到了,因而就产生了闭包的情况,不会在内存中被销毁,而是一直留着。

等到它们都执行完毕后,最终内存回收会将之全部都销毁。

其实以上画的图并不是很严谨,与实际在内存中的表现肯定是有差异的,但是对于理解闭包在内存里的情况还是不影响的。

闭包能用在哪?

首先需要先明确一点,那就是在JavaScript中,只要创建了函数,其实就产生了闭包。这是广义上的闭包,因为在全局作用域下声明的函数,也会记着全局作用域。而不是只有在函数内部声明的函数才叫做闭包。

通常意义上所讨论的闭包,是使用了闭包的特性

1. 函数作为返回值

let a = 1function outer() {  let a = 2

  function inside() {
    a += 1
    console.log(a)
  }  return inside
}const foo = outer()foo()
ログイン後にコピー

此处outer函数调用完时,返回了一个inside函数,在执行上下文栈中表示的既是outer函数执行上下文被销毁,但有一个返回值是一个函数。 该函数在内存中创建了一个空间,其[[scope]]指向着outer函数的作用域。因而outer函数的环境不会被销毁。

当foo函数开始调用时,调用的就是inside函数,所以它在执行时,先询问自身作用域是否存在变量a, 不存在则向上询问自己的父作用域outer,存在变量a且值为2,最终输出3。

2. 函数作为参数

var name = &#39;xavier&#39;function foo() {  var name = &#39;parker&#39;
  function bar() {    console.log(name)
  } console.log(name)  return bar
}function baz(fn) {  var name = &#39;coin&#39;
  fn()
}baz(foo())baz(foo)
ログイン後にコピー

对于第一个baz函数调用,输出的结果为两个'parker'。 对于第二个baz函数的调用,输出为一个'parker'。

具体的理解其实跟上面一致,只要函数被其他函数调用,都会存在闭包。

3. 私有属性

闭包可以实现对于一些属性的隐藏,外部只能获取到属性,但是无法对属性进行操作。

function foo(name) {  let _name = name  return {    get: function() {      return _name
    }
  }
}let obj = foo(&#39;xavier&#39;)
obj.get()
ログイン後にコピー

4. 高阶函数,科里化,节流防抖等

对于一些需要存在状态的函数,都是使用到了闭包的特性。

// 节流function throttle(fn, timeout) {  let timer = null
  return function (...arg) {    if(timer) return
    timer = setTimeout(() => {
    fn.apply(this, arg)
    timer = null
    }, timeout)
  }
}// 防抖function debounce(fn, timeout){  let timer = null
  return function(...arg){    clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, arg)
    }, timeout)
  }
}
ログイン後にコピー

5. 模块化

在没有模块之前,对于不同地方声明的变量,可能会产生冲突。而闭包能够创造出一个封闭的私有空间,为模块化提供了可能性。 可以使用IIFE+闭包实现模块。

var moduleA = (function (global, doc) {  var methodA = function() {};  var dataA = {};  return {    methodA: methodA,    dataA: dataA
  };
})(this, document);
ログイン後にコピー

【相关推荐:JavaScript视频教程web前端

以上がJavaScript 概要共有の閉鎖の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.im
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート