برنامج تعليمي لإغلاق JavaScript - مع كود مثال لإغلاق JS

عمليات الإغلاق - ربما سمع العديد من مطوري JavaScript هذا المصطلح من قبل. عندما بدأت رحلتي مع JavaScript ، واجهت حالات إغلاق في كثير من الأحيان. وأعتقد أنها من أهم المفاهيم وأكثرها إثارة للاهتمام في JavaScript.

ألا تعتقد أنها مثيرة للاهتمام؟ يحدث هذا غالبًا عندما لا تفهم مفهومًا - لا تجده ممتعًا. (لا أعرف ما إذا كان هذا يحدث لك أم لا ، ولكن هذا هو الحال معي).

لذا في هذه المقالة ، سأحاول أن أجعل عمليات الإغلاق ممتعة بالنسبة لك.

قبل الدخول في عالم الإغلاق ، دعونا أولاً نفهم النطاق المعجمي . إذا كنت تعرف ذلك بالفعل ، فتخط الجزء التالي. وإلا فانتقل إليه لفهم عمليات الإغلاق بشكل أفضل.

النطاق المعجمي

قد تفكر - أعرف النطاق المحلي والعالمي ، ولكن ما هو النطاق المعجمي؟ كنت أتصرف بنفس الطريقة عندما سمعت هذا المصطلح. لا تقلق! دعونا نلقي نظرة فاحصة.

الأمر بسيط مثل النطاقين الآخرين:

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. لماذا ا؟

لأنه في تحديد النطاق الديناميكي ، يتم البحث في الوظيفة المحلية أولاً ، ثم ينتقل إلى الوظيفة التي تسمى تلك الوظيفة المحلية. ثم يقوم بالبحث في الوظيفة التي تسمى تلك الوظيفة ، وما إلى ذلك ، في مكدس الاستدعاءات.

اسمها واضح بذاته - "ديناميكي" يعني التغيير. يمكن أن يختلف نطاق المتغير وقيمته لأنه يعتمد على مكان استدعاء الوظيفة. يمكن أن يتغير معنى المتغير في وقت التشغيل.

حصلت على جوهر النطاق الديناميكي؟ إذا كانت الإجابة بنعم ، فتذكر فقط أن النطاق المعجمي هو عكس ذلك.

في تحديد النطاق المعجمي، والبحث يجري في وظيفة المحلية أولا، ثم يذهب إلى الداخل وظيفة التي أن يتم تعريف وظيفة. بعد ذلك يبحث في وظيفة داخله أن يعرف وظيفة وهلم جرا.

لذلك ، فإن تحديد النطاق المعجمي أو الثابت يعني أن نطاق وقيمة المتغير يتم تحديده من حيث يتم تعريفه. لا يتغير.

دعنا نلقي نظرة مرة أخرى على المثال أعلاه ونحاول معرفة الإخراج بنفسك. تطور واحد فقط - أعلن 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. المتغيرات العالمية.

انتظر! هل هذا هو تعريف الإغلاق أم النطاق المعجمي؟ كلا التعريفين يبدوان متشابهين. كيف هم مختلفون؟

حسنًا ، لهذا السبب حددت النطاق المعجمي أعلاه. لأن عمليات الإغلاق مرتبطة بالنطاق المعجمي / الثابت.

دعنا ننظر مرة أخرى إلى تعريفه الآخر الذي سيخبرك كيف تختلف عمليات الإغلاق.

يحدث الإغلاق عندما تكون الوظيفة قادرة على الوصول إلى نطاقها المعجمي ، حتى عندما يتم تنفيذ هذه الوظيفة خارج نطاقها المعجمي.

أو،

يمكن للوظائف الداخلية الوصول إلى نطاقها الأصلي ، حتى بعد تنفيذ الوظيفة الأصلية بالفعل.

مشوش؟ لا تقلق إذا لم تكن قد فهمت هذه النقطة بعد. لدي أمثلة لمساعدتك على فهم أفضل. دعنا نعدل المثال الأول من تحديد النطاق المعجمي:

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

الفرق في هذا الرمز هو أننا نعيد الوظيفة الداخلية وننفذها لاحقًا. في بعض لغات البرمجة ، يوجد المتغير المحلي أثناء تنفيذ الوظيفة. ولكن بمجرد تنفيذ الوظيفة ، لا توجد هذه المتغيرات المحلية ولن يمكن الوصول إليها.

لكن المشهد مختلف هنا. بعد تنفيذ الوظيفة الأصلية ، لا يزال بإمكان الوظيفة الداخلية (الدالة المرتجعة) الوصول إلى متغيرات الوظيفة الأصلية. نعم ، خمنت بشكل صحيح. الإغلاق هو السبب.

تحافظ الوظيفة الداخلية على نطاقها المعجمي عندما يتم تنفيذ الوظيفة الأصلية ، وبالتالي ، يمكن لاحقًا لهذه الوظيفة الداخلية الوصول إلى تلك المتغيرات.

للحصول على إحساس أفضل به ، دعنا نستخدم dir()طريقة وحدة التحكم للنظر في قائمة خصائص callGreetCustomer:

console.dir(callGreetCustomer);

من الصورة أعلاه ، يمكنك أن ترى كيف تحافظ الوظيفة الداخلية على نطاقها الأصلي ( customerName) عند greetCustomer()تنفيذها. وفي وقت لاحق ، تم استخدامه customerNameعندما callGreetCustomer()تم إعدامه.

آمل أن يكون هذا المثال قد ساعدك في فهم التعريف أعلاه للإغلاق بشكل أفضل. وربما تجد الآن الإغلاق أكثر متعة.

ماذا بعد؟ لنجعل هذا الموضوع أكثر تشويقًا من خلال النظر في أمثلة مختلفة.

أمثلة على عمليات الإغلاق في العمل

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

في كل مرة تتصل فيها countValue، تزداد قيمة متغير العد بمقدار 1. انتظر - هل تعتقد أن قيمة العد هي 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.

هذا هو! أتمنى أن تقول الآن أنك تجد الإغلاق مثيرًا للاهتمام.

لقراءة مقالاتي الأخرى ، تحقق من ملف التعريف الخاص بي هنا.