اختصارات JavaScript للمبتدئين

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

المواد ، التي ننشر ترجمتها اليوم ، مخصصة لقصة الآليات الداخلية للإغلاق وكيفية عملها في برامج JavaScript.


ما هو الإغلاق؟


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

ما هي البيئة المعجمية؟


يشير مصطلح "بيئة معجمية" أو "بيئة ثابتة" في جافا سكريبت إلى القدرة على الوصول إلى المتغيرات والوظائف والكائنات بناءً على موقعها الفعلي في شفرة المصدر. فكر في مثال:

let a = 'global';  function outer() {    let b = 'outer';    function inner() {      let c = 'inner'      console.log(c);   // 'inner'      console.log(b);   // 'outer'      console.log(a);   // 'global'    }    console.log(a);     // 'global'    console.log(b);     // 'outer'    inner();  } outer(); console.log(a);         // 'global' 

هنا ، يمكن للدالة inner() الوصول إلى المتغيرات المعلنة في نطاقها الخاص ، في نطاق الوظيفة outer() ، وفي النطاق العالمي. يمكن للدالة outer() الوصول إلى المتغيرات المعلنة في نطاقها وفي النطاق العالمي.

ستبدو سلسلة نطاق الكود أعلاه كما يلي:

 Global { outer {   inner } } 

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

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


خذ بعين الاعتبار ، قبل تفكيك تعقيدات الدوائر الداخلية ، بعض الأمثلة العملية.

▍ مثال رقم 1


 function person() { let name = 'Peter'; return function displayName() {   console.log(name); }; } let peter = person(); peter(); // 'Peter' 

هنا ندعو الدالة person() ، التي تُرجع الدالة الداخلية displayName() ، وتخزن هذه الوظيفة في المتغير peter . عندما ، بعد ذلك ، نسمي وظيفة peter() (يخزن المتغير المقابل بالفعل مرجعًا للدالة displayName() ) ، يتم عرض الاسم Peter في وحدة التحكم.

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

▍ مثال رقم 2


 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());  // 0 console.log(count());  // 1 console.log(count());  // 2 

هنا ، كما في المثال السابق ، نقوم بتخزين الرابط إلى الوظيفة الداخلية المجهولة التي getCounter() الدالة getCounter() في count المتغيرات. نظرًا لأن الدالة count() هي عبارة عن إغلاق ، يمكنها الوصول إلى متغير counter getCount() حتى بعد getCounter() وظيفة getCounter() عملها.

لاحظ أنه لا يتم إعادة تعيين قيمة متغير counter إلى 0 في كل مرة يتم استدعاء الدالة count() . قد يبدو أنه يجب إعادة تعيينها إلى 0 ، كما هو الحال عند استدعاء وظيفة عادية ، ولكن هذا لا يحدث.

يعمل هذا تمامًا لأنه في كل مرة يتم استدعاء الدالة count() ، يتم إنشاء نطاق جديد لها ، ولكن لا يوجد سوى نطاق واحد getCounter() . نظرًا لأن متغير counter مُعلن في نطاق getCounter() ، يتم حفظ قيمته بين استدعاءات دالة count() بدون إعادة التعيين إلى 0.

كيف تعمل الدوائر القصيرة؟


حتى الآن ، تحدثنا عن ماهية عمليات الإغلاق ، وفحصنا الأمثلة العملية. الآن دعونا نتحدث عن آليات JavaScript الداخلية التي تجعلها تعمل.

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

context سياق التنفيذ


سياق التنفيذ هو بيئة مجردة يتم فيها حساب تعليمات JavaScript البرمجية وتنفيذها. عندما يتم تنفيذ التعليمات البرمجية العمومية ، يحدث هذا داخل سياق التنفيذ العام. يتم تنفيذ رمز الوظيفة في سياق تنفيذ الوظيفة.

في وقت ما ، يمكن تنفيذ التعليمات البرمجية في سياق تنفيذ واحد فقط (JavaScript هي لغة برمجة مفردة). تتم إدارة هذه العمليات باستخدام ما يسمى Call Stack.

مكدس الاستدعاء عبارة عن بنية بيانات مرتبة وفقًا لمبدأ LIFO (Last In ، First Out - Last In ، First Out). لا يمكن وضع عناصر جديدة إلا في الجزء العلوي من المكدس ، ويمكن إزالة العناصر فقط منها.

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

خذ بعين الاعتبار المثال التالي لفهم سياق التنفيذ ومكدس الاستدعاء بشكل أفضل:


مثال سياق التنفيذ

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

يبدو مكدس الاستدعاء لهذا الرمز كما يلي:


مكدس الاستدعاء

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

environment البيئة المعجمية


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

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

تحتوي البيئة المعجمية على مكونين:

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

من الناحية النظرية ، تبدو البيئة المعجمية كما يلي:

 lexicalEnvironment = { environmentRecord: {   <identifier> : <value>,   <identifier> : <value> } outer: < Reference to the parent lexical environment> } 

ألق نظرة على مقتطف الشفرة التالي:

 let a = 'Hello World!'; function first() { let b = 25;  console.log('Inside first function'); } first(); console.log('Inside global execution context'); 

عندما ينشئ محرك JS سياق تنفيذ عالميًا لتنفيذ التعليمات البرمجية العامة ، فإنه يخلق أيضًا بيئة معجمية جديدة لتخزين المتغيرات والوظائف المعلنة في النطاق العالمي. ونتيجة لذلك ، ستبدو البيئة المعجمية للنطاق العالمي كما يلي:

 globalLexicalEnvironment = { environmentRecord: {     a : 'Hello World!',     first : < reference to function object > } outer: null } 

يرجى ملاحظة أنه تم تعيين الإشارة إلى البيئة المعجمية الخارجية ( outer ) على قيمة null ، لأن النطاق العالمي لا يحتوي على بيئة معجمية خارجية.

عندما ينشئ المحرك سياق تنفيذ للدالة first() ، فإنه يخلق أيضًا بيئة معجمية لتخزين المتغيرات المعلنة في هذه الوظيفة أثناء تنفيذها. ونتيجة لذلك ، ستبدو البيئة المعجمية للدالة كما يلي:

 functionLexicalEnvironment = { environmentRecord: {     b : 25, } outer: <globalLexicalEnvironment> } 

يتم تعيين الارتباط إلى البيئة المعجمية الخارجية للدالة إلى <globalLexicalEnvironment> ، حيث إن كود الوظيفة في كود المصدر في النطاق العام.

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

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


الآن بعد أن سلحنا أنفسنا بمعرفة سياق التنفيذ والبيئة المعجمية ، سنعود إلى عمليات الإغلاق ونحلل بمزيد من العمق نفس أجزاء الكود التي فحصناها بالفعل.

▍ مثال رقم 1


ألق نظرة على مقتطف الرمز هذا:

 function person() { let name = 'Peter'; return function displayName() {   console.log(name); }; } let peter = person(); peter(); // 'Peter' 

عندما يتم تنفيذ الوظيفة person() ، يقوم محرك JS بإنشاء سياق تنفيذ جديد وبيئة معجمية جديدة لهذه الوظيفة. عند الانتهاء من العمل ، تقوم الدالة بإرجاع الدالة displayName() ، ويتم كتابة مرجع لهذه الوظيفة إلى peter المتغير.

ستبدو بيئتها المعجمية كما يلي:

 personLexicalEnvironment = { environmentRecord: {   name : 'Peter',   displayName: < displayName function reference> } outer: <globalLexicalEnvironment> } 

عند خروج الدالة person() ، يتم بروز سياق التنفيذ الخاص بها من المكدس. لكن بيئتها المعجمية تظل في الذاكرة ، نظرًا لوجود ارتباط بها في البيئة المعجمية لوظيفتها الداخلية displayName() . ونتيجة لذلك ، تظل المتغيرات المعلنة في هذه البيئة المعجمية متاحة.

عندما يتم استدعاء دالة peter() (يخزن المتغير المقابل إشارة إلى وظيفة displayName() ) ، يقوم محرك JS بإنشاء سياق تنفيذ جديد وبيئة معجمية جديدة لهذه الوظيفة. ستبدو هذه البيئة المعجمية كما يلي:

 displayNameLexicalEnvironment = { environmentRecord: {   } outer: <personLexicalEnvironment> } 

لا توجد متغيرات في دالة displayName() ، لذلك سيكون سجل البيئة فارغًا. أثناء تنفيذ هذه الوظيفة ، سيحاول محرك JS العثور على متغير name في البيئة المعجمية للدالة.

نظرًا لأنه لا يمكن العثور على البحث في البيئة المعجمية لوظيفة displayName() ، سيستمر البحث في البيئة المعجمية الخارجية ، أي في البيئة المعجمية لوظيفة person() ، التي لا تزال في الذاكرة. هناك ، يعثر المحرك على المتغير المطلوب ويعرض قيمته في وحدة التحكم.

▍ مثال رقم 2


 function getCounter() { let counter = 0; return function() {   return counter++; } } let count = getCounter(); console.log(count());  // 0 console.log(count());  // 1 console.log(count());  // 2 

getCounter() البيئة المعجمية getCounter() كما يلي:

 getCounterLexicalEnvironment = { environmentRecord: {   counter: 0,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 

تُرجع هذه الدالة دالة مجهولة يتم تعيينها لمتغير count .

عندما يتم تنفيذ الدالة count() ، تبدو بيئتها المعجمية كما يلي:

 countLexicalEnvironment = { environmentRecord: { } outer: <getCountLexicalEnvironment> } 

عند أداء هذه الوظيفة ، سيبحث النظام عن متغير counter في بيئته المعجمية. في هذه الحالة ، مرة أخرى ، سجل بيئة الوظيفة فارغ ، لذلك يستمر البحث عن المتغير في البيئة المعجمية الخارجية للدالة.

يعثر المحرك على المتغير ، ويعرضه في وحدة التحكم ، getCounter() متغير counter ، الذي يتم تخزينه في البيئة المعجمية getCounter() .

نتيجة لذلك ، getCounter() البيئة المعجمية getCounter() بعد أول استدعاء للدالة count() كما يلي:

 getCounterLexicalEnvironment = { environmentRecord: {   counter: 1,   <anonymous function> : < reference to function> } outer: <globalLexicalEnvironment> } 

في كل مرة يتم استدعاء الدالة count() ، يقوم محرك JavaScript بإنشاء بيئة معجمية جديدة لهذه الوظيفة getCounter() متغير counter ، مما يؤدي إلى تغييرات في البيئة المعجمية getCounter() .

الملخص


في هذه المقالة ، تحدثنا عن عمليات الإغلاق وفرزنا آليات جافا سكريبت الأساسية التي تقوم عليها. تعتبر عمليات الإغلاق واحدة من أهم مفاهيم JavaScript الأساسية ، ويجب على كل مطور JS فهمها. يُعد فهم عمليات الإغلاق إحدى خطوات كتابة تطبيقات فعالة وعالية الجودة.

أعزائي القراء! إذا كانت لديك خبرة في تطوير JS ، فالرجاء مشاركة الأمثلة العملية لاستخدام الإغلاق مع المبتدئين.

Source: https://habr.com/ru/post/ar424967/


All Articles