ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript のクロージャを詳しく見る

JavaScript のクロージャを詳しく見る

PHPz
PHPz転載
2016-05-16 16:32:512017ブラウズ

この記事では、JavaScript のクロージャについて紹介します。困っている友人が参考になれば幸いです。

JavaScript のクロージャを詳しく見る

クロージャ - 非常に重要ですが理解するのが難しい概念です。クロージャを理解することは、ある意味、再生とも言えます - 「あなたは Js を知りません》
皆さんも閉鎖を見るのにうんざりしているかもしれませんが、それでもまとめてみたいと思います! ! !

1. クロージャとは

その名の通り、問題に遭遇したとき、まず「なぜ?」を考えてみましょう。答えてみてください:

  • クロージャーは関数内のサブ関数です - 等于没说

  • 関数が覚えていて、関数が現在の字句スコープ外で実行された場合でも、クロージャ。 ——靠谱

  • クロージャは、他の関数の内部変数を読み取ることができる関数であり、本質的には関数の内部リンクと外部リンクの間のブリッジです。 >靠谱

  • 関数とその周囲の状態 (字句環境) への参照がバンドルされてクロージャーを形成します -

    很靠谱

を見てみましょう「コードを使用して上記の回答を説明し、

お気に入り~

1.1 クロージャは関数内のサブ関数です

」を使用してみてください。まずこのコードを見てください:

function foo(params) {
    var a = '余光';

    function bar() {
        console.log(a);
    }
    bar()
}

foo(); // 余光
字句スコープの検索ルールに基づいて、

bar函数 変数を正常に出力できます。これは a のサブ関数でもありますが、厳密にはそうではありません。クロージャの概念は明確に表現されていません。foo ネストされた関数は、大きな外部スコープで宣言された変数 にアクセスできると言ったほうが正確です。

1.2 クロージャは、他の関数の内部変数を読み取ることができる関数です。本質的には、内部関数と関数の外部リンクの間のブリッジです。

次の例では、

function foo(params) {
    var a = '余光';

    function bar() {
        console.log(a);
    }
    return bar;
}

var res = foo();
res(); // 余光
の結果は一貫しています。これは、

res 関数の実行時に返される foo 参照であり、bar 関数がその字句環境を保存できるためです。 bar

1.3 関数とその周囲の状態 (字句環境) への参照がバンドルされてクロージャを形成します

次のコードを見てみましょう:

var name = '余光';

function foo() {
  console.log(name); // 余光 
}

foo(); //余光
foo のコンテキストは静的に保存され、関数の作成時に保存されます。以下でそれを確認してみましょう:

var name = '余光';

function foo() {
  console.log(name); // 余光
}

(function (func) {
    var name = '老王';

    func()
})(foo); // 余光
ここで理解できます。 に保存します。 > すでに漠然としているので、「JavaScript の実行コンテキスト」という記事を数分かけて読んだほうがよいでしょう。 [[scope]][[scope]]1.4 概要

注: クロージャが関数内で返されるサブ関数であるという記述は、それ自体は正しいです

が、それは依存します。内容 開始点: ECMAScript では、クロージャは次のことを指します:

    理論的な観点から見ると、すべての関数です。それらはすべて、作成時に上位コンテキストのデータを保存するためです。これは、関数内のグローバル変数へのアクセスは自由変数へのアクセスと同等であるため、単純なグローバル変数にも当てはまります。このとき、最も外側のスコープが使用されます。
  • 実用的な観点から: 次の関数はクロージャとみなされます:
  • それが作成されたコンテキストが破棄された場合でも、まだ存在します (例: 、内部関数が関数内で返されます)
    • はコード内の自由変数を参照します
要約:

クローズド パッケージのコード ブロックは、コード ブロックのコンテキストでデータの組み合わせを作成します。
  • クロージャーは、他の関数の内部変数を読み取ることができる関数であり、本質的には他の関数間のブリッジです。関数の内部リンクと外部リンク
  • 視点が異なれば、クロージャの解釈も異なります
  • 注: これらはすべてのクロージャではありません。クロージャとは何か、と尋ねられたときと同じです。このトピックを終了しないと、さらに多くのトピックが発生する可能性があります。

JavaScript のクロージャを詳しく見る

2. クロージャーを分析してみます

これはまだ古典的なコードです:

最初このコードでは、実行コンテキスト スタックと実行コンテキストの変更を分析します。
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo(); // local scope

    グローバル コードを入力し、グローバル実行コンテキストを作成し、グローバル実行コンテキストを実行コンテキスト スタックにプッシュします
  • グローバル実行コンテキストの初期化
  • checkscope 関数の実行、checkscope 関数の実行コンテキストの作成、checkscope 実行コンテキストが実行コンテキスト スタックにプッシュされます
  • checkscope 実行コンテキストの初期化、変数の作成object、scopechain、this など。
  • checkscope 関数が実行され、checkscope 実行コンテキストが実行コンテキスト スタックからポップされます
  • 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈

  • f 执行上下文初始化,创建变量对象、作用域链、this等

  • f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

JavaScript のクロージャを詳しく見る

f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?

当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

因为这个作用域链:

  • f 函数依然可以读取到 checkscopeContext.AO 的值;
  • f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,JavaScript 依然会让 checkscopeContext.AO 活在内存中;
  • f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

多么浪漫的思想——只要你需要我,那我我本应该被销毁,你也能找到我~

JavaScript のクロージャを詳しく見る

三、经典问题

3.1 多个对象引用同一个[[Scope]],你遇到过吗?

直接上代码:

var child1;
var child2;
function parent() {
    var x = 1;

    child1 = function () {
        console.log(++x)
    };
    child2 = function () {
        console.log(--x)
    };
}
parent();
child1(); // 2
child1(); // 3
child2(); // 2

大家可能不理解,child1child他们两个函数在创建后都保存了上层上下文,万万没想到,同一个上下文创建的闭包是共用一个[[scope]]属性的,某个闭包对其中[[Scope]]的变量做修改会影响到其他闭包对其变量的读取。

3.2 闭包轻松解决的经典问题

大家一定对下面这段代码很眼熟:

var arr = []
for(var i = 0; i < 10; i++){
    arr[i] = function () {
        console.log(i)
    }
}
arr[0](); // 10
arr[1](); // 10
arr[2](); // 10
arr[3](); // 10

我们这么解释它:同一个上下文中创建的闭包是共用一个[[Scope]]属性的

因此上层上下文中的变量i是可以很容易就被改变的。

arr[0],arr[1]…arr[9]他们共用一个[[scope]],最终执行的时候结果当然一样。

如何利用闭包来解决这个问题呢?

var arr = []
for(var i = 0; i < 10; i++){
    arr[i] = (function (i) {
        return function () {
            console.log(i);
        }
    })(i)
}
arr[0](); // 0
arr[1](); // 1
arr[2](); // 2
arr[3](); // 3

我们通过立即执行匿名函数的方式隔离了作用域,当执行 arr[0] 函数的时候,arr[0] 函数的作用域链发生了改变:

arr[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

匿名函数Context = {
    AO: {
        arguments: {
            0: 0,
            length: 1
        },
        i: 0
    }
}

我们看到,这时函数的[[Scope]]属性就有了真正想要的值了,为了达到这样的目的,我们不得不在[[Scope]]中创建额外的变量对象。要注意的是,在返回的函数中,如果要获取i的值,那么该值还是会是10。

3.3 总结

  • 函数内的所有内部函数都共享一个父作用域,因此创建的闭包是共用的。
  • 利用闭包隔离作用域的特性可以解决共享作用域的问题

推荐学习:《PHP视频教程

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