JavaScriptクロージャチュートリアル–JSクロージャサンプルコード付き

クロージャ– JavaScript開発者の多くは、おそらく以前にこの用語を聞いたことがあるでしょう。JavaScriptを使って旅を始めたとき、クロージャに頻繁に遭遇しました。そして、それらはJavaScriptで最も重要で興味深い概念の1つだと思います。

あなたは彼らが面白いとは思わないのですか?これは、概念を理解していない場合によく発生します。概念が面白くない場合です。(これがあなたに起こるかどうかはわかりませんが、これは私の場合です)。

したがって、この記事では、クロージャを興味深いものにするように努めます。

クロージャの世界に入る前に、まず字句スコープを理解しましょう。すでに知っている場合は、次の部分をスキップしてください。それ以外の場合は、クロージャをよりよく理解するためにそれに飛び込んでください。

字句スコープ

あなたは考えているかもしれません-私はローカルスコープとグローバルスコープを知っていますが、字句スコープとは一体何ですか?この言葉を聞いたときも同じように反応しました。心配無用!よく見てみましょう。

他の2つのスコープと同じように簡単です。

function greetCustomer() { var customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); // Hi! anchal } greetingMsg(); }

上記の出力から、内部関数が外部関数の変数にアクセスできることがわかります。これは字句スコープであり、変数のスコープと値は、変数が定義/作成された場所(つまり、コード内での位置)によって決定されます。とった?

私は最後のビットがあなたを混乱させたかもしれないことを知っています。だから私はあなたをより深く連れて行きましょう。字句スコープは静的スコープとしても知られていることをご存知ですか?はい、それは別の名前です。

一部のプログラミング言語がサポートする動的スコープもあります。なぜ動的スコープについて言及したのですか?字句スコープをよりよく理解するのに役立つからです。

いくつかの例を見てみましょう。

function greetingMsg() { console.log(customerName);// ReferenceError: customerName is not defined } function greetCustomer() { var customerName = "anchal"; greetingMsg(); } greetCustomer();

出力に同意しますか?はい、参照エラーが発生します。これは、両方の関数が別々に定義されているため、両方の関数が互いのスコープにアクセスできないためです。

別の例を見てみましょう:

function addNumbers(number1) { console.log(number1 + number2); } function addNumbersGenerate() { var number2 = 10; addNumbers(number2); } addNumbersGenerate();

上記の出力は、動的スコープの言語の場合は20になります。字句スコープをサポートする言語はreferenceError: number2 is not defined。どうして?

動的スコープでは、検索は最初にローカル関数で行われ、次にそのローカル関数を呼び出した関数に入るためです。そして、それが呼ばれることを関数内で検索している機能など、コールスタックアップ。

その名前は自明です–「動的」は変化を意味します。変数のスコープと値は、関数が呼び出される場所によって異なるため、異なる場合があります。変数の意味は実行時に変更される可能性があります。

動的スコープの要点を理解しましたか?はいの場合は、字句スコープがその反対であることを覚えておいてください。

字句スコープでは、検索は最初にローカル関数で行われ、次にその関数が定義されている関数に入ります。次に、その関数が定義されている関数など検索します。

したがって、字句スコープまたは静的スコープは、変数のスコープと値が定義された場所から決定されることを意味します。変わらない。

上記の例をもう一度見て、自分で出力を理解してみましょう。たった1つのひねり–number2上部で宣言します。

var number2 = 2; function addNumbers(number1) { console.log(number1 + number2); } function addNumbersGenerate() { var number2 = 10; addNumbers(number2); } addNumbersGenerate(); 

出力がどうなるか知っていますか?

正解–レキシカルスコープの言語の場合は12です。これは、最初にaddNumbers関数(最も内側のスコープ)を調べてから、この関数が定義されている内部を検索するためです。number2変数を取得すると、出力は12になります。

なぜ私がここで字句スコープに多くの時間を費やしたのか不思議に思うかもしれません。これはクロージャの記事であり、字句スコープに関する記事ではありません。しかし、字句スコープについて知らなければ、クロージャを理解することはできません。

どうして?クロージャの定義を見ると、あなたはあなたの答えを得るでしょう。それでは、トラックに入り、閉鎖に戻りましょう。

クロージャとは何ですか?

クロージャの定義を見てみましょう。

クロージャは、内部関数が外部関数の変数と引数にアクセスできるときに作成されます。内部関数は–にアクセスできます

1.独自の変数。

2.外部関数の変数と引数。

3.グローバル変数。

待つ!これはクロージャまたは字句スコープの定義ですか?どちらの定義も同じように見えます。それらはどのように違うのですか?

さて、それが私が上記の字句スコープを定義した理由です。クロージャは字句/静的スコープに関連しているためです。

クロージャがどのように異なるかを示す他の定義をもう一度見てみましょう。

クロージャとは、関数が字句スコープの外で実行されている場合でも、関数が字句スコープにアクセスできる場合です。

または、

親関数がすでに実行された後でも、内部関数はその親スコープにアクセスできます。

Confused? Don't worry if you haven't yet gotten the point. I have examples to help you better understand. Let’s modify the first example of lexical scoping:

function greetCustomer() { const customerName = "anchal"; function greetingMsg() { console.log("Hi! " + customerName); } return greetingMsg; } const callGreetCustomer = greetCustomer(); callGreetCustomer(); // output – Hi! anchal

The difference in this code is that we are returning the inner function and executing it later. In some programming languages, the local variable exists during the function’s execution. But once the function is executed, those local variables don’t exist and they will not be accessible.

Here, however, the scene is different. After the parent function is executed, the inner function (returned function) can still access the parent function's variables. Yes, you guessed right. Closures are the reason.

The inner function preserves its lexical scope when the parent function is executing and hence, later that inner function can access those variables.

To get a better feel for it, let’s use the dir() method of the console to look into the list of the properties of callGreetCustomer:

console.dir(callGreetCustomer);

From the above image, you can see how the inner function preserves its parent scope (customerName) when greetCustomer() is executed. And later on, it used customerName when callGreetCustomer() was executed.

I hope this example helped you better understand the above definition of a closure. And maybe now you find closures a bit more fun.

So what next? Let’s make this topic more interesting by looking at different examples.

Examples of closures in action

function counter() { let count = 0; return function() { return count++; }; } const countValue = counter(); countValue(); // 0 countValue(); // 1 countValue(); // 2

Every time you call countValue, the count variable value is incremented by 1. Wait – did you think that the value of count is 0?

Well, that would be wrong as a closure doesn’t work with a value. It stores the reference of the variable. That’s why, when we update the value, it reflects in the second or third call and so on as the closure stores the reference.

Feeling a bit clearer now? Let’s look at another example:

function counter() { let count = 0; return function () { return count++; }; } const countValue1 = counter(); const countValue2 = counter(); countValue1(); // 0 countValue1(); // 1 countValue2(); // 0 countValue2(); // 1 

I hope you guessed the right answer. If not, here is the reason. As countValue1 and countValue2, both preserve their own lexical scope. They have independent lexical environments. You can use dir() to check the [[scopes]] value in both the cases.

Let’s look at a third example.

This one's a bit different. In it, we have to write a function to achieve the output:

const addNumberCall = addNumber(7); addNumberCall(8) // 15 addNumberCall(6) // 13

Simple. Use your newly-gained closure knowledge:

function addNumber(number1) { return function (number2) { return number1 + number2; }; }

Now let’s look at some tricky examples:

function countTheNumber() { var arrToStore = []; for (var x = 0; x < 9; x++) { arrToStore[x] = function () { return x; }; } return arrToStore; } const callInnerFunctions = countTheNumber(); callInnerFunctions[0]() // 9 callInnerFunctions[1]() // 9

Every array element that stores a function will give you an output of 9. Did you guess right? I hope so, but still let me tell you the reason. This is because of the closure's behavior.

The closure stores the reference, not the value. The first time the loop runs, the value of x is 0. Then the second time x is 1, and so on. Because the closure stores the reference, every time the loop runs it's changing the value of x. And at last, the value of x will be 9. So callInnerFunctions[0]() gives an output of 9.

But what if you want an output of 0 to 8? Simple! Use a closure.

Think about it before looking at the solution below:

function callTheNumber() { function getAllNumbers(number) { return function() { return number; }; } var arrToStore = []; for (var x = 0; x < 9; x++) { arrToStore[x] = getAllNumbers(x); } return arrToStore; } const callInnerFunctions = callTheNumber(); console.log(callInnerFunctions[0]()); // 0 console.log(callInnerFunctions[1]()); // 1

Here, we have created separate scope for each iteration. You can use console.dir(arrToStore) to check the value of x in [[scopes]] for different array elements.

That’s it! I hope you can now say that you find closures interesting.

To read my other articles, check out my profile here.