سؤال

كيف تشرح عمليات إغلاق JavaScript لشخص لديه معرفة بالمفاهيم التي تتكون منها (على سبيل المثال، الوظائف والمتغيرات وما شابه ذلك)، لكنه لا يفهم عمليات الإغلاق نفسها؟

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

هل كانت مفيدة؟

المحلول

إغلاق جافا سكريبت للمبتدئين

قدمه موريس يوم الثلاثاء 2006-02-21 10:19.تم تحريره من قبل المجتمع منذ ذلك الحين.

عمليات الإغلاق ليست سحرية

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

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

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

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

ملخصين مختصرين

  • عندما تكون الدالة (foo) تعلن عن دوال أخرى (bar وbaz)، وهي عائلة المتغيرات المحلية التي تم إنشاؤها فيها foo يكون لم يتم تدميرها عندما تخرج الوظيفة.تصبح المتغيرات غير مرئية للعالم الخارجي. foo وبالتالي يمكن إرجاع الوظائف بمكر bar و baz, ، ويمكنهم الاستمرار في القراءة والكتابة والتواصل مع بعضهم البعض من خلال هذه العائلة المنغلقة من المتغيرات ("الإغلاق") التي لا يمكن لأي شخص آخر التدخل فيها، ولا حتى شخص يتصل بها foo مرة أخرى في المستقبل.

  • الإغلاق هو أحد طرق الدعم وظائف من الدرجة الأولى;إنه تعبير يمكنه الإشارة إلى المتغيرات ضمن نطاقه (عندما تم الإعلان عنه لأول مرة)، أو تعيينه إلى متغير، أو تمريره كوسيطة إلى دالة، أو إرجاعه كنتيجة دالة.

مثال على الإغلاق

يُرجع التعليمة البرمجية التالية مرجعًا إلى دالة:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

سوف يفهم معظم مبرمجي JavaScript كيفية إرجاع مرجع إلى دالة إلى متغير (say2) في الكود أعلاه.إذا لم تقم بذلك، فأنت بحاجة إلى إلقاء نظرة على ذلك قبل أن تتمكن من تعلم عمليات الإغلاق.قد يفكر المبرمج الذي يستخدم لغة C في الوظيفة على أنها إرجاع مؤشر إلى دالة، وأن المتغيرات say و say2 كان كل منها مؤشرًا لوظيفة.

هناك فرق حاسم بين مؤشر C للدالة ومرجع JavaScript للدالة.في JavaScript، يمكنك اعتبار المتغير المرجعي للدالة بمثابة مؤشر إلى دالة أيضًا كمؤشر مخفي للإغلاق.

يحتوي الكود أعلاه على إغلاق لأن الوظيفة المجهولة function() { console.log(text); } أعلن داخل وظيفة أخرى، sayHello2() في هذا المثال.في جافا سكريبت، إذا كنت تستخدم function الكلمة الرئيسية داخل وظيفة أخرى، أنت تقوم بإنشاء إغلاق.

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

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

function() { console.log(text); } // Output of say2.toString();

النظر في إخراج say2.toString(), يمكننا أن نرى أن الكود يشير إلى المتغير text.يمكن الرجوع إلى الوظيفة المجهولة text الذي يحمل القيمة 'Hello Bob' لأن المتغيرات المحلية ل sayHello2() وقد تم الاحتفاظ بها سرا على قيد الحياة في الإغلاق.

تكمن العبقرية في أن مرجع الوظيفة في JavaScript يحتوي أيضًا على مرجع سري للإغلاق الذي تم إنشاؤه فيه - على غرار الطريقة التي يكون بها المفوضون مؤشر أسلوب بالإضافة إلى مرجع سري لكائن ما.

مزيد من الأمثلة

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

مثال 3

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

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

مثال 4

جميع الوظائف العالمية الثلاث لها إشارة مشتركة إلى نفس إغلاق لأنه تم الإعلان عنها جميعًا خلال مكالمة واحدة لـ setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

تشترك الوظائف الثلاث في الوصول إلى نفس الإغلاق - المتغيرات المحلية لـ setupSomeGlobals() عندما تم تحديد الوظائف الثلاث.

لاحظ أنه في المثال أعلاه، إذا قمت بالاتصال setupSomeGlobals() مرة أخرى، يتم إنشاء إغلاق جديد (إطار مكدس!).القديم gLogNumber, gIncreaseNumber, gSetNumber تتم الكتابة فوق المتغيرات مع جديد الوظائف التي لها الإغلاق الجديد.(في JavaScript، عندما تعلن عن وظيفة داخل وظيفة أخرى، يتم إعادة إنشاء الوظيفة (الوظائف) الداخلية مرة أخرى كل الوقت الذي يتم فيه استدعاء الوظيفة الخارجية.)

مثال 5

يوضح هذا المثال أن الإغلاق يحتوي على أي متغيرات محلية تم الإعلان عنها داخل الوظيفة الخارجية قبل خروجها.لاحظ أن المتغير alice تم الإعلان عنه فعليًا بعد الوظيفة المجهولة.يتم الإعلان عن الوظيفة المجهولة أولاً وعندما يتم استدعاء هذه الوظيفة، يمكنها الوصول إلى alice متغير بسبب alice موجود في نفس النطاق (JavaScript يفعل ذلك رفع متغير).أيضًا sayAlice()() فقط يستدعي مباشرة مرجع الوظيفة الذي تم إرجاعه من sayAlice() — هو بالضبط نفس ما تم في السابق ولكن بدون المتغير المؤقت.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

مخادع:لاحظ ال say المتغير موجود أيضًا داخل الإغلاق ويمكن الوصول إليه بواسطة أي وظيفة أخرى قد يتم الإعلان عنها بداخله sayAlice(), أو يمكن الوصول إليه بشكل متكرر داخل الوظيفة الداخلية.

مثال 6

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

أنت بحاجة إلى فهم ميزة "الرفع المتغير" في Javascript حتى تتمكن من فهم هذا المثال.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

الخط result.push( function() {console.log(item + ' ' + list[i])} يضيف مرجعًا إلى دالة مجهولة ثلاث مرات إلى مصفوفة النتائج.إذا لم تكن على دراية بالوظائف المجهولة، ففكر في الأمر على النحو التالي:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

لاحظ أنه عند تشغيل المثال، "item2 undefined" تم تسجيله ثلاث مرات!وذلك لأنه كما هو الحال في الأمثلة السابقة، هناك إغلاق واحد فقط للمتغيرات المحلية لـ buildList (و هو result, i, list و item).عندما يتم استدعاء الوظائف المجهولة على الخط fnlist[j]();يستخدمون جميعًا نفس الإغلاق الفردي، ويستخدمون القيمة الحالية لـ i و item ضمن هذا الإغلاق الواحد (حيث i لديه قيمة 3 لأن الحلقة قد اكتملت، و item لديه قيمة 'item2').لاحظ أننا نقوم بالفهرسة من 0 وبالتالي item لديه قيمة item2.وسوف يزيد i++ i إلى القيمة 3.

قد يكون من المفيد معرفة ما يحدث عند الإعلان عن المتغير على مستوى الكتلة item يتم استخدامه (عبر let الكلمة الأساسية) بدلاً من إعلان متغير على نطاق الوظيفة عبر var الكلمة الرئيسية.إذا تم إجراء هذا التغيير، فسيتم حذف كل دالة مجهولة في المصفوفة result لها إغلاقها الخاص.عندما يتم تشغيل المثال يكون الإخراج كما يلي:

item0 undefined
item1 undefined
item2 undefined

إذا كان المتغير i يتم تعريفه أيضًا باستخدام let بدلاً من var, ، فالإخراج هو:

item0 1
item1 2
item2 3

مثال 7

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

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

ملخص

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

النقاط النهائية:

  • كلما استخدمت function داخل وظيفة أخرى، يتم استخدام الإغلاق.
  • كلما استخدمت eval() داخل الدالة، يتم استخدام الإغلاق.النص لك eval يمكن الرجوع إلى المتغيرات المحلية للوظيفة، وداخلها eval يمكنك حتى إنشاء متغيرات محلية جديدة باستخدام eval('var foo = …')
  • عندما تستخدم new Function(…) (ال منشئ الوظيفة) داخل الدالة، لا يؤدي ذلك إلى إنشاء إغلاق.(لا يمكن للوظيفة الجديدة الرجوع إلى المتغيرات المحلية للوظيفة الخارجية.)
  • يشبه الإغلاق في JavaScript الاحتفاظ بنسخة من جميع المتغيرات المحلية، تمامًا كما كانت عند خروج الوظيفة.
  • ربما يكون من الأفضل الاعتقاد بأن الإغلاق يتم إنشاؤه دائمًا مجرد إدخال إلى دالة، ويتم إضافة المتغيرات المحلية إلى هذا الإغلاق.
  • يتم الاحتفاظ بمجموعة جديدة من المتغيرات المحلية في كل مرة يتم فيها استدعاء دالة ذات إغلاق (نظرًا لأن الدالة تحتوي على إعلان دالة بداخلها، ويتم إما إرجاع مرجع لتلك الدالة الداخلية أو الاحتفاظ بمرجع خارجي لها بطريقة ما) ).
  • قد تبدو وظيفتان وكأن لهما نفس النص المصدر، لكن لهما سلوك مختلف تمامًا بسبب إغلاقهما "المخفي".لا أعتقد أن كود JavaScript يمكنه بالفعل معرفة ما إذا كان مرجع الوظيفة به إغلاق أم لا.
  • إذا كنت تحاول إجراء أي تعديلات على كود المصدر الديناميكي (على سبيل المثال: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));)، فإنه لن يعمل إذا myFunction هو إغلاق (بالطبع، لن تفكر أبدًا في إجراء استبدال سلسلة التعليمات البرمجية المصدر في وقت التشغيل، ولكن...).
  • من الممكن الحصول على إعلانات الوظائف ضمن إعلانات الوظائف داخل الوظائف... ويمكنك الحصول على عمليات إغلاق على أكثر من مستوى.
  • أعتقد عادةً أن الإغلاق هو مصطلح لكل من الوظيفة والمتغيرات التي تم التقاطها.لاحظ أنني لا أستخدم هذا التعريف في هذه المقالة!
  • أظن أن عمليات الإغلاق في JavaScript تختلف عن تلك الموجودة عادةً في اللغات الوظيفية.

روابط

شكرًا

اذا كنت تمتلك فقط تعلمت عمليات الإغلاق (هنا أو في أي مكان آخر!)، فأنا مهتم بأي تعليقات منك حول أي تغييرات قد تقترحها والتي قد تجعل هذه المقالة أكثر وضوحًا.أرسل بريدًا إلكترونيًا إلى morrisjohns.com (morris_closure @).يرجى ملاحظة أنني لست خبيرًا في جافا سكريبت - ولا في عمليات الإغلاق.


يمكن العثور على المشاركة الأصلية لموريس في أرشيف الإنترنت.

نصائح أخرى

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

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

سيؤدي هذا دائمًا إلى تسجيل 16، لأنه bar يمكن الوصول إلى x والتي تم تعريفها على أنها حجة ل foo, ، ويمكن الوصول إليه أيضًا tmp من foo.

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

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

ستقوم الوظيفة المذكورة أعلاه أيضًا بتسجيل 16، لأن bar لا يزال من الممكن الرجوع إليها x و tmp, ، على الرغم من أنه لم يعد داخل النطاق مباشرة.

لكن منذ tmp لا يزال يتسكع في الداخل barإغلاق ، ويجري أيضا زيادته.سيتم زيادتها في كل مرة تتصل فيها bar.

أبسط مثال على الإغلاق هو:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

عند استدعاء وظيفة JavaScript، يتم إنشاء سياق تنفيذ جديد.بالإضافة إلى وسيطات الدالة والكائن الأصلي، يتلقى سياق التنفيذ هذا أيضًا جميع المتغيرات المعلنة خارجه (في المثال أعلاه، كل من 'a' و'b').

من الممكن إنشاء أكثر من دالة إغلاق، إما عن طريق إرجاع قائمة بها أو عن طريق ضبطها على المتغيرات العامة.كل هذه سوف تشير إلى نفس x ونفس الشيء tmp, ، فهم لا يصنعون نسخًا خاصة بهم.

هنا الرقم x هو رقم حرفي.كما هو الحال مع القيم الحرفية الأخرى في JavaScript، متى foo يسمى الرقم x يكون نسخ داخل foo كحجة لها x.

من ناحية أخرى، تستخدم JavaScript دائمًا المراجع عند التعامل مع الكائنات.إذا قلت، اتصلت foo مع كائن ما، سيتم الإغلاق الذي يعود إليه مرجع هذا الكائن الأصلي!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

كما هو متوقع، كل مكالمة ل bar(10) سوف تزيد x.memb.ما قد لا يتوقعه هو ذلك x يشير ببساطة إلى نفس الكائن مثل age عامل!بعد عدة مكالمات إلى bar, age.memb سيكون 2!هذا المرجع هو الأساس لتسرب الذاكرة مع كائنات HTML.

مقدمة:تمت كتابة هذه الإجابة عندما كان السؤال:

كما قال ألبرت القديم:"إذا لم تتمكن من شرح الأمر لطفل يبلغ من العمر ست سنوات، فأنت في الحقيقة لا تفهمه بنفسك."حسنًا، لقد حاولت شرح عمليات إغلاق JS لصديق يبلغ من العمر 27 عامًا وفشلت تمامًا.

هل يمكن لأي شخص أن يعتبر أنني في السادسة من عمري وأنني مهتم بشكل غريب بهذا الموضوع؟

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


أنا من أشد المعجبين بالتشبيه والاستعارة عند شرح المفاهيم الصعبة، لذا اسمحوا لي أن أجرب قصة.

كان ياما كان:

كانت هناك أميرة...

function princess() {

عاشت في عالم رائع مليئ بالمغامرات.التقت بأميرها تشارمينغ، وتجولت حول عالمها على متن وحيد القرن، وحاربت التنانين، وواجهت حيوانات ناطقة، والعديد من الأشياء الخيالية الأخرى.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

لكن سيتعين عليها دائمًا العودة إلى عالمها الممل المليء بالأعمال المنزلية والكبار.

    return {

وكانت تخبرهم في كثير من الأحيان عن أحدث مغامراتها المذهلة كأميرة.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

لكن كل ما يرونه هو فتاة صغيرة...

var littleGirl = princess();

...سرد قصص عن السحر والخيال.

littleGirl.story();

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

ولكننا نعرف الحقيقة الحقيقية؛أن الفتاة الصغيرة مع الأميرة في الداخل...

...هي حقًا أميرة بداخلها فتاة صغيرة.

إذا أخذنا السؤال على محمل الجد، يجب أن نكتشف ما يستطيع طفل نموذجي يبلغ من العمر 6 سنوات أن يفعله معرفيًا، على الرغم من أنه من المسلم به أن الشخص المهتم بجافا سكريبت ليس نموذجيًا.

على تنمية الطفولة:من 5 إلى 7 سنوات انها تقول:

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

يمكننا استخدام هذا المثال لشرح عمليات الإغلاق، كما يلي:

المطبخ عبارة عن إغلاق له متغير محلي يسمى trashBags.هناك وظيفة داخل المطبخ تسمى getTrashBag الذي يحصل على كيس قمامة واحد ويعيده.

يمكننا ترميز هذا في JavaScript مثل هذا:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

نقاط أخرى تشرح سبب كون عمليات الإغلاق مثيرة للاهتمام:

  • كل مرة makeKitchen() يتم استدعاء إغلاق جديد منفصل خاص به trashBags.
  • ال trashBags المتغير محلي داخل كل مطبخ ولا يمكن الوصول إليه من الخارج، ولكن الوظيفة الداخلية موجودة فيه getTrashBag الملكية لديها حق الوصول إليها.
  • يُنشئ كل استدعاء دالة إغلاقًا، ولكن لن تكون هناك حاجة للاحتفاظ بالإغلاق ما لم يمكن استدعاء وظيفة داخلية، يمكنها الوصول إلى داخل الإغلاق، من خارج الإغلاق.إرجاع الكائن مع getTrashBag الوظيفة تفعل ذلك هنا.

رجل القش

أحتاج إلى معرفة عدد المرات التي تم فيها النقر على الزر والقيام بشيء ما في كل نقرة ثالثة...

حل واضح إلى حد ما

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

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

النظر في هذا الخيار

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

لاحظ بعض الأشياء هنا.

في المثال أعلاه، أستخدم سلوك الإغلاق لجافا سكريبت. يسمح هذا السلوك لأي وظيفة بالوصول إلى النطاق الذي تم إنشاؤها فيه، إلى أجل غير مسمى. لتطبيق هذا عمليًا، أقوم فورًا باستدعاء دالة تُرجع دالة أخرى، ولأن الوظيفة التي أرجعها لديها حق الوصول إلى متغير العدد الداخلي (بسبب سلوك الإغلاق الموضح أعلاه) يؤدي هذا إلى نطاق خاص للاستخدام بواسطة الناتج وظيفة...ليس بسيط جدا؟دعونا نخففها ...

إغلاق بسيط من سطر واحد

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

جميع المتغيرات خارج الدالة التي تم إرجاعها متاحة للدالة التي تم إرجاعها، ولكنها ليست متاحة مباشرة لكائن الدالة التي تم إرجاعها...

func();  // Alerts "val"
func.a;  // Undefined

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

أيضًا، هذه الحالة المتغيرة الخاصة هي تماما يمكن الوصول إليه، لكل من القراءات والتعيين لمتغيراته الخاصة ذات النطاق.

ها أنت ذا؛أنت الآن تقوم بتغليف هذا السلوك بالكامل.

مشاركة مدونة كاملة (بما في ذلك اعتبارات jQuery)

من الصعب تفسير عمليات الإغلاق لأنها تُستخدم لإنجاح بعض السلوكيات التي يتوقع الجميع بشكل حدسي أن تنجح على أي حال.أجد أفضل طريقة لشرحها (والطريقة التي أنا تعلمت ما يفعلون) هو تخيل الوضع بدونهم:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

ماذا سيحدث هنا إذا تم استخدام JavaScript لم يفعل ذلك تعرف الإغلاقات؟ما عليك سوى استبدال المكالمة الموجودة في السطر الأخير بنص الطريقة الخاص بها (وهو ما تفعله استدعاءات الوظائف بشكل أساسي) وستحصل على:

console.log(x + 3);

والآن أين تعريف x؟ولم نحدده في النطاق الحالي.الحل الوحيد هو السماح plus5 يحمل نطاقه (أو بالأحرى نطاق الشركة الأم) حوله.من هنا، x محددة جيدًا وهي مرتبطة بالقيمة 5.

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

  • لا يتم إنشاء الإغلاق فقط عند إرجاع وظيفة داخلية. في الواقع، وظيفة التضمين لا يحتاج إلى العودة على الإطلاق من أجل إنشاء إغلاقه.يمكنك بدلاً من ذلك تعيين وظيفتك الداخلية لمتغير في نطاق خارجي، أو تمريرها كوسيطة إلى وظيفة أخرى حيث يمكن استدعاؤها على الفور أو في أي وقت لاحق.ولذلك، فمن المحتمل أن يتم إنشاء إغلاق الدالة التضمينية بمجرد استدعاء الدالة التضمين نظرًا لأن أي وظيفة داخلية لديها حق الوصول إلى هذا الإغلاق عندما يتم استدعاء الوظيفة الداخلية، قبل أو بعد عودة الوظيفة المتضمنة.
  • لا يشير الإغلاق إلى نسخة من القيم القديمة للمتغيرات في نطاقها. تعد المتغيرات نفسها جزءًا من الإغلاق، وبالتالي فإن القيمة التي يتم رؤيتها عند الوصول إلى أحد هذه المتغيرات هي أحدث قيمة في وقت الوصول إليها.هذا هو السبب في أن الوظائف الداخلية التي تم إنشاؤها داخل الحلقات يمكن أن تكون صعبة، حيث أن كل واحدة منها لديها حق الوصول إلى نفس المتغيرات الخارجية بدلاً من الحصول على نسخة من المتغيرات في وقت إنشاء الوظيفة أو استدعائها.
  • تتضمن "المتغيرات" في الإغلاق أي وظائف مسماة أعلن داخل الوظيفة.وهي تتضمن أيضًا وسائط الدالة.يتمتع الإغلاق أيضًا بإمكانية الوصول إلى متغيرات الإغلاق التي يحتوي عليها، وصولاً إلى النطاق العالمي.
  • تستخدم عمليات الإغلاق الذاكرة، ولكنها لا تسبب تسربًا للذاكرة نظرًا لأن JavaScript تقوم بنفسها بتنظيف بنياتها الدائرية التي لم يتم الرجوع إليها.يتم إنشاء تسربات ذاكرة Internet Explorer التي تتضمن عمليات إغلاق عندما يفشل في فصل قيم سمات DOM التي تشير إلى عمليات الإغلاق، وبالتالي الحفاظ على المراجع إلى الهياكل الدائرية المحتملة.

حسنا، مروحة الإغلاق البالغة من العمر 6 سنوات.هل تريد أن تسمع أبسط مثال على الإغلاق؟

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

إليكم كيف يمكنني تحويل قصة طائرتي إلى رمز.

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

أ إنهاء يشبه إلى حد كبير كائن.يتم إنشاء مثيل له عند استدعاء وظيفة.

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

يوجد متغير في إنهاء اذا أنت

  1. تعيينه مع var foo=1; أو
  2. اكتب فقط var foo;

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

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

مثال

function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

انتاج |

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

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

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

وبهذا المعنى، فإنهم يسمحون للوظيفة بالتصرف مثل كائن ذي سمات خاصة.

المشاركة الكاملة:

إذن ما هي هذه الأشياء الختامية؟

عمليات الإغلاق بسيطة:

يغطي المثال البسيط التالي جميع النقاط الرئيسية لإغلاقات JavaScript.*  

هنا مصنع ينتج الآلات الحاسبة التي يمكنها الجمع والضرب:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

النقطة الأساسية: كل دعوة ل make_calculator إنشاء متغير محلي جديد n, ، والذي لا يزال قابلاً للاستخدام بواسطة تلك الآلة الحاسبة add و multiply وظائف بعد فترة طويلة make_calculator عائدات.

إذا كنت معتادًا على الإطارات المكدسة، فستبدو هذه الآلات الحاسبة غريبة:كيف يمكنهم الاستمرار في الوصول n بعد make_calculator عائدات؟الجواب هو أن نتخيل أن JavaScript لا تستخدم "إطارات مكدسة"، ولكنها تستخدم بدلاً من ذلك "إطارات الكومة"، والتي يمكن أن تستمر بعد استدعاء الوظيفة الذي جعلها تعود.

وظائف داخلية مثل add و multiply, ، والتي تصل إلى المتغيرات المعلنة في دالة خارجية**, ، وتسمى الإغلاق.

هذا هو إلى حد كبير كل ما في الأمر لعمليات الإغلاق.



* على سبيل المثال، فهو يغطي جميع النقاط الواردة في مقالة "إغلاقات الدمى" الواردة في إجابة أخرى, ، باستثناء المثال 6، الذي يوضح ببساطة أنه يمكن استخدام المتغيرات قبل الإعلان عنها، وهي حقيقة لطيفة يجب معرفتها ولكنها لا علاقة لها بالإغلاق على الإطلاق.كما يغطي جميع النقاط الموجودة فيه الجواب المقبول, ، باستثناء النقاط (1) التي تقوم الدالة بنسخ وسيطاتها إلى متغيرات محلية (وسائط الدالة المسماة)، و(2) أن نسخ الأرقام يؤدي إلى إنشاء رقم جديد، ولكن نسخ مرجع كائن يمنحك مرجعًا آخر لنفس الكائن.من الجيد أيضًا معرفة هذه الأمور ولكنها مرة أخرى لا علاقة لها تمامًا بعمليات الإغلاق.وهو أيضًا مشابه جدًا للمثال الموجود في هذه الإجابة ولكن أقصر قليلا وأقل تجريدا.ولا يغطي نقطة هذه الإجابة أو هذا التعليق, ، وهو أن JavaScript تجعل من الصعب توصيل ملف حاضِر قيمة متغير الحلقة في وظيفتك الداخلية:لا يمكن تنفيذ خطوة "التوصيل" إلا باستخدام وظيفة مساعدة تتضمن وظيفتك الداخلية ويتم استدعاؤها في كل تكرار للحلقة.(بالمعنى الدقيق للكلمة، تصل الوظيفة الداخلية إلى نسخة المتغير الخاصة بالوظيفة المساعدة، بدلاً من توصيل أي شيء.) مرة أخرى، هذا مفيد جدًا عند إنشاء عمليات الإغلاق، ولكنه ليس جزءًا من ماهية الإغلاق أو كيفية عمله.هناك ارتباك إضافي بسبب عمليات الإغلاق التي تعمل بشكل مختلف في اللغات الوظيفية مثل تعلم الآلة، حيث ترتبط المتغيرات بالقيم بدلاً من مساحة التخزين، مما يوفر دفقًا مستمرًا من الأشخاص الذين يفهمون عمليات الإغلاق بطريقة (أي طريقة "التوصيل") ببساطة غير صحيح بالنسبة لجافا سكريبت، حيث ترتبط المتغيرات دائمًا بمساحة التخزين، وليس بالقيم أبدًا.

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

كيف أشرح ذلك لطفل في السادسة من عمره:

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

هل يمكنك شرح عمليات الإغلاق لطفل يبلغ من العمر 5 سنوات؟*

ما زلت أعتقد شرح جوجل يعمل بشكل جيد للغاية وموجز:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

Proof that this example creates a closure even if the inner function doesn

* سؤال في لغة C#

أميل إلى التعلم بشكل أفضل من خلال مقارنات الجيد/السيئ.أحب أن أرى رمز العمل متبوعًا برمز غير عامل من المحتمل أن يواجهه شخص ما.لقد جمعت jsFiddle يقوم بإجراء مقارنة ويحاول اختصار الاختلافات إلى أبسط التفسيرات التي يمكنني التوصل إليها.

عمليات الإغلاق تمت بشكل صحيح:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • في الكود أعلاه createClosure(n) يتم استدعاؤه في كل تكرار للحلقة.لاحظ أنني قمت بتسمية المتغير n لتسليط الضوء على أنه أ جديد المتغير الذي تم إنشاؤه في نطاق وظيفة جديد وليس هو نفس المتغير index الذي يرتبط بالنطاق الخارجي.

  • وهذا يخلق نطاقا جديدا و n يرتبط بهذا النطاق؛هذا يعني أن لدينا 10 نطاقات منفصلة، ​​نطاق واحد لكل تكرار.

  • createClosure(n) تقوم بإرجاع دالة تُرجع n ضمن هذا النطاق.

  • داخل كل نطاق n لا بد من أي قيمة كان لها عندما createClosure(n) تم استدعاؤه لذا فإن الوظيفة المتداخلة التي يتم إرجاعها ستُرجع دائمًا قيمة n أنه كان عندما createClosure(n) تم استدعاؤه.

عمليات الإغلاق تمت بشكل خاطئ:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • في الكود أعلاه تم نقل الحلقة داخل ملف createClosureArray() function وتقوم الدالة الآن بإرجاع المصفوفة المكتملة فقط، والتي تبدو للوهلة الأولى أكثر سهولة.

  • ما قد لا يكون واضحا هو أنه منذ ذلك الحين createClosureArray() يتم استدعاؤه فقط بمجرد إنشاء نطاق واحد فقط لهذه الوظيفة بدلاً من نطاق واحد لكل تكرار للحلقة.

  • ضمن هذه الدالة متغير اسمه index ويعرف.تعمل الحلقة وتضيف وظائف إلى المصفوفة التي ترجع index.لاحظ أن index يتم تعريفه ضمن createClosureArray الوظيفة التي يتم استدعاؤها مرة واحدة فقط.

  • لأنه لم يكن هناك سوى نطاق واحد داخل createClosureArray() وظيفة، index يرتبط فقط بقيمة ضمن هذا النطاق.بمعنى آخر، في كل مرة تغير فيها الحلقة قيمة index, فإنه يغيره لكل ما يشير إليه ضمن هذا النطاق.

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

  • بعد انتهاء الحلقة و index تم تعديل القيمة النهائية وكانت 10، وبالتالي فإن كل دالة تضاف إلى المصفوفة ترجع قيمة الفرد index المتغير الذي تم ضبطه الآن على 10.

نتيجة

تم الإغلاق بشكل صحيح
ن = 0
ن = 1
ن = 2
ن = 3
ن = 4
ن = 5
ن = 6
ن = 7
ن = 8
ن = 9

عمليات الإغلاق تمت بشكل خاطئ
ن = 10
ن = 10
ن = 10
ن = 10
ن = 10
ن = 10
ن = 10
ن = 10
ن = 10
ن = 10

ويكيبيديا عن الإغلاقات:

في علوم الكمبيوتر، الإغلاق هو دالة مع بيئة مرجعية للأسماء غير المحلية (المتغيرات الحرة) لتلك الوظيفة.

من الناحية الفنية، في جافا سكريبت, كل وظيفة هي إغلاق.لديه دائمًا إمكانية الوصول إلى المتغيرات المحددة في النطاق المحيط.

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

غالبًا ما تُستخدم عمليات الإغلاق لإنشاء وظائف تحتوي على بعض البيانات الخاصة المخفية (ولكن هذا ليس هو الحال دائمًا).

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

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

لقد قمت بتجميع برنامج تعليمي تفاعلي لجافا سكريبت لشرح كيفية عمل عمليات الإغلاق.ما هو الإغلاق؟

إليك أحد الأمثلة:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

سيتذكر الأطفال دائمًا الأسرار التي شاركوها مع والديهم ، حتى بعد رحيل والديهم.هذا هو ما هي عمليات الإغلاق للوظائف.

أسرار وظائف JavaScript هي المتغيرات الخاصة

var parent = function() {
 var name = "Mary"; // secret
}

في كل مرة تقوم باستدعاءه، يتم إنشاء المتغير المحلي "name" ويعطى الاسم "Mary".وفي كل مرة تخرج فيها الدالة يتم فقدان المتغير ونسيان الاسم.

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

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

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

لذا، طالما أننا في الوظيفة الأم، فيمكن إنشاء وظيفة فرعية واحدة أو أكثر والتي تتشارك في المتغيرات السرية من المكان السري.

لكن الشيء المحزن هو أنه إذا كان الطفل أيضًا متغيرًا خاصًا لوظيفته الأم، فإنه سيموت أيضًا عندما ينتهي الوالد، وتموت الأسرار معه.

لذا، لكي يعيش، يجب على الطفل أن يغادر قبل فوات الأوان

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

والآن، على الرغم من أن ماري "لم تعد تعمل"، إلا أن ذكراها لم تضيع وسيتذكر طفلها دائمًا اسمها والأسرار الأخرى التي شاركاها خلال وقتهما معًا.

لذا، إذا ناديت الطفلة بـ "أليس"، فسوف تستجيب

child("Alice") => "My name is Alice, child of Mary"

هذا كل ما يمكن قوله.

لا أفهم لماذا الإجابات معقدة للغاية هنا.

هنا إغلاق:

var a = 42;

function b() { return a; }

نعم.ربما تستخدم ذلك عدة مرات في اليوم.


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

الآن ما عليه يسمح ما يمكنك القيام به يمكن أن يكون أكثر إثارة، راجع الإجابات الأخرى.

مثال للنقطة الأولى بواسطة dlaliberte:

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

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

أعلم أن هناك الكثير من الحلول بالفعل، ولكن أعتقد أن هذا البرنامج النصي الصغير والبسيط يمكن أن يكون مفيدًا لتوضيح المفهوم:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

أنت تنام وتدعو دان.أخبر دان أن يحضر وحدة تحكم XBox واحدة.

دان يدعو بول.يطلب دان من بول إحضار وحدة تحكم واحدة.كم عدد المراقبين الذين تم جلبهم للحزب؟

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

يمكن لوظائف JavaScript الوصول إلى:

  1. الحجج
  2. السكان المحليون (أي المتغيرات المحلية والوظائف المحلية)
  3. البيئة، والتي تشمل:
    • العولمة، بما في ذلك DOM
    • أي شيء في الوظائف الخارجية

إذا وصلت إحدى الوظائف إلى بيئتها، فستكون الوظيفة بمثابة إغلاق.

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

مثال على الإغلاق الذي يستخدم البيئة العالمية:

تخيل أنه تم تنفيذ حدثي زر Stack Overflow Vote-Up وVote-Down كعمليات إغلاق، VoteUp_click وvoteDown_click، التي لها حق الوصول إلى المتغيرات الخارجية isVotedUp وisVotedDown، والتي تم تعريفها عالميًا.(من أجل البساطة، أشير إلى أزرار التصويت على الأسئلة في StackOverflow، وليس مجموعة أزرار التصويت على الإجابة.)

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

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

جميع هذه الوظائف الأربع عبارة عن عمليات إغلاق لأنها تصل جميعها إلى بيئتها.

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

ماذا لو تم الوصول إلى متغير، لكنه ليس محليًا؟مثلما هو الحال هنا:

Enter image description here

في هذه الحالة ، يجد المترجم المتغير في الخارجي LexicalEnvironment هدف.

تتكون العملية من خطوتين:

  1. أولاً ، عند إنشاء وظيفة f ، لا يتم إنشاؤها في مساحة فارغة.يوجد كائن LexicalEnvironment الحالي.في الحالة أعلاه ، إنها نافذة (A غير محددة في وقت إنشاء الوظيفة).

Enter image description here

عندما يتم إنشاء دالة، فإنها تحصل على خاصية مخفية، تسمى [[Scope]]، والتي تشير إلى LexicalEnvironment الحالي.

Enter image description here

إذا تمت قراءة متغير، ولكن لا يمكن العثور عليه في أي مكان، فسيتم إنشاء خطأ.

وظائف متداخلة

يمكن أن تتداخل الوظائف مع بعضها البعض، لتشكل سلسلة من البيئات المعجمية والتي يمكن أيضًا أن تسمى سلسلة النطاق.

Enter image description here

إذن، الدالة g لديها حق الوصول إلى g وa وf.

الإغلاق

قد تستمر الوظيفة المتداخلة في العيش بعد انتهاء الوظيفة الخارجية:

Enter image description here

ترميز البيئات المعجمية:

Enter image description here

كما نرى، this.say هي خاصية في كائن المستخدم، لذلك تستمر في العيش بعد اكتمال المستخدم.

وإذا كنت تتذكر، متى this.say يتم إنشاؤها، (مثل كل وظيفة) تحصل على مرجع داخلي this.say.[[Scope]] إلى البيئة المعجمية الحالية.لذلك، تبقى البيئة المعجمية لتنفيذ المستخدم الحالي في الذاكرة.جميع متغيرات المستخدم هي أيضًا خصائصه، لذلك يتم الاحتفاظ بها بعناية أيضًا، وليس التخلص منها كما هو معتاد.

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

كي تختصر:

  1. تعمل الوظيفة الداخلية على الإشارة إلى البيئة المعجمية الخارجية.
  2. قد تصل الوظيفة الداخلية إلى متغيراتها في أي وقت حتى إذا تم الانتهاء من الوظيفة الخارجية.
  3. يحتفظ المتصفح بـ LexicalEnvironment وجميع خصائصه (المتغيرات) في الذاكرة حتى تكون هناك وظيفة داخلية تشير إليه.

وهذا ما يسمى الإغلاق.

باعتباري أبًا لطفل يبلغ من العمر 6 سنوات، ويقوم حاليًا بتعليم الأطفال الصغار (ومبتدئ نسبيًا في البرمجة دون أي تعليم رسمي لذلك ستكون هناك حاجة إلى تصحيحات)، أعتقد أن الدرس سيبقى أفضل من خلال اللعب العملي.إذا كان الطفل البالغ من العمر 6 سنوات مستعدًا لفهم ماهية الإغلاق، فهو كبير بما يكفي ليجربه بنفسه.أقترح لصق الكود في jsfiddle.net، وشرحه قليلًا، وتركه بمفرده لتأليف أغنية فريدة.ربما يكون النص التوضيحي أدناه أكثر ملاءمة لعمر 10 سنوات.

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

تعليمات

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

شفرة:كل ما هو مكتوب أعلاه يسمى شفرة.هو مكتوب بلغة جافا سكريبت.

جافا سكريبت:جافا سكريبت هي لغة.مثل اللغة الإنجليزية أو الفرنسية أو الصينية هي لغات.هناك الكثير من اللغات التي تفهمها أجهزة الكمبيوتر والمعالجات الإلكترونية الأخرى.لكي يفهم الكمبيوتر جافا سكريبت، فإنه يحتاج إلى مترجم.تخيل لو أن مدرسًا يتحدث اللغة الروسية فقط يأتي لتدريس صفك في المدرسة.عندما يقول المعلم "Все садятся"، فإن الفصل لن يفهم.لكن لحسن الحظ أن لديك تلميذًا روسيًا في صفك يخبر الجميع أن هذا يعني "الجميع يجلسون" - وهكذا تفعلون جميعًا.يشبه الفصل الدراسي الكمبيوتر، والتلميذ الروسي هو المترجم الفوري.بالنسبة لجافا سكريبت، المترجم الأكثر شيوعًا يسمى المتصفح.

المتصفح:عندما تتصل بالإنترنت على جهاز كمبيوتر أو جهاز لوحي أو هاتف لزيارة موقع ويب، فإنك تستخدم متصفحًا.من الأمثلة التي قد تعرفها هي Internet Explorer وChrome وFirefox وSafari.يمكن للمتصفح فهم JavaScript وإخبار الكمبيوتر بما يجب عليه فعله.تسمى تعليمات JavaScript بالوظائف.

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

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

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

تحتوي الوظيفة عادةً على اسم وأقواس وأقواس.مثله:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

لاحظ أن /*...*/ و // رمز التوقف عن القراءة بواسطة المتصفح.

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

الأقواس:"الأقواس" أو () هي صندوق البريد الموجود على باب مصنع وظائف JavaScript أو صندوق بريد في الشارع لإرسال حزم المعلومات إلى المصنع.في بعض الأحيان قد يتم وضع علامة على صندوق البريد على سبيل المثال cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime), ، وفي هذه الحالة تعرف البيانات التي يتعين عليك تقديمها لها.

الأقواس:"الأقواس" التي تبدو هكذا {} هي النوافذ الملونة لمصنعنا.من داخل المصنع يمكنك رؤية الخارج، لكن من الخارج لا يمكنك رؤية الداخل.

مثال الكود الطويل أعلاه

يبدأ الكود الخاص بنا بالكلمة وظيفة, ، فنعلم أنه واحد!ثم اسم الدالة يغني - هذا هو وصفي الخاص لما تدور حوله الوظيفة.ثم بين قوسين ().الأقواس موجودة دائمًا للدالة.أحيانًا تكون فارغة، وأحيانًا يكون بها شيء ما.وهذا فيه كلمة: (person).وبعد هذا هناك قوس مثل هذا { .هذا يمثل بداية الوظيفة يغني().لديها شريك الذي يمثل نهاية يغني() مثله }

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

لذلك قد يكون لهذه الوظيفة علاقة بالغناء، وقد تحتاج إلى بعض البيانات عن الشخص.لديها تعليمات بالداخل لفعل شيء ما بهذه البيانات.

الآن، بعد الوظيفة يغني(), ، بالقرب من نهاية الكود يوجد السطر

var person="an old lady";

عامل:الرسائل فار الوقوف على "متغير".المتغير يشبه المظروف.من الخارج، يحمل هذا الظرف علامة "شخص".يحتوي من الداخل على قصاصة من الورق تحتوي على المعلومات التي تحتاجها وظيفتنا، وبعض الحروف والمسافات متصلة معًا مثل قطعة من الخيط (يُطلق عليها اسم الخيط) التي تشكل عبارة "سيدة عجوز".يمكن أن يحتوي مظروفنا على أنواع أخرى من الأشياء مثل الأرقام (تسمى الأعداد الصحيحة)، والتعليمات (وتسمى الوظائف)، والقوائم (وتسمى صفائف).لأن هذا المتغير مكتوب خارج كل الأقواس {}, ولأنه يمكنك الرؤية من خلال النوافذ الملونة عندما تكون داخل الأقواس، يمكن رؤية هذا المتغير من أي مكان في الكود.نحن نسمي هذا "المتغير العالمي".

المتغير العالمي: شخص هو متغير عالمي، مما يعني أنه إذا قمت بتغيير قيمته من "سيدة عجوز" إلى "شاب"، فإن شخص سيظل شابًا حتى تقرر تغييره مرة أخرى، ويمكن لأي وظيفة أخرى في الكود أن ترى أنه شاب.اضغط على F12 زر أو انظر إلى إعدادات الخيارات لفتح وحدة تحكم المطور في المتصفح واكتب "شخص" لمعرفة هذه القيمة.يكتب person="a young man" لتغييره ثم اكتب "شخص" مرة أخرى لترى أنه قد تغير.

بعد هذا لدينا الخط

sing(person);

هذا الخط يستدعي الدالة، كما لو كان يستدعي كلبًا

"تعال يغني, ، تعال واحصل شخص!"

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

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

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

جميع عمليات الإغلاق تعرف ما يغني() متغير الدالة يسمى الجزء الاول لأنهم يستطيعون الرؤية من خلال نوافذهم الملونة.

بعد الإغلاق تأتي الخطوط

fly();
spider();
bird();
cat();

ستقوم الدالة Sing() باستدعاء كل من هذه الوظائف بالترتيب المعطا لها.ثم سيتم الانتهاء من عمل وظيفة الغناء ().

حسنًا، عند التحدث مع طفل يبلغ من العمر 6 سنوات، ربما أستخدم الارتباطات التالية.

تخيل أنك تلعب مع إخوتك وأخواتك الصغار في المنزل بأكمله، وتتنقل بألعابك وأحضرت بعضها إلى غرفة أخيك الأكبر.بعد فترة عاد أخوك من المدرسة وذهب إلى غرفته، وقد أغلق داخلها، فلم يعد بإمكانك الآن الوصول إلى الألعاب المتبقية هناك بطريقة مباشرة.لكن يمكنك أن تطرق الباب وتطلب من أخيك تلك الألعاب.وهذا ما يسمى لعبة إنهاء;لقد عوضك أخوك، وهو الآن في الخارج نِطَاق.

قارن ذلك بالموقف الذي تم فيه قفل الباب بتيار هوائي وعدم وجود أحد بالداخل (تنفيذ وظيفة عامة)، ثم حدث حريق محلي وأحرق الغرفة (جامع القمامة:D)، وبعد ذلك تم بناء غرفة جديدة ويمكنك الآن المغادرة ألعاب أخرى هناك (مثال وظيفة جديدة)، ولكن لا تحصل أبدًا على نفس الألعاب التي تركت في مثيل الغرفة الأولى.

بالنسبة للطفل المتقدم أود أن أضع شيئًا مثل ما يلي.إنه ليس مثاليًا، لكنه يجعلك تشعر بما هو عليه:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

كما ترون، الألعاب المتبقية في الغرفة لا يزال من الممكن الوصول إليها عن طريق الأخ وبغض النظر عما إذا كانت الغرفة مغلقة.هنا jsbin للعب معها.

إجابة لطفل يبلغ من العمر ست سنوات (بافتراض أنه يعرف ما هي الدالة وما هو المتغير وما هي البيانات):

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

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

هناك طريقة أخرى بسيطة حقًا لشرح ذلك من حيث النطاق:

في أي وقت تقوم فيه بإنشاء نطاق أصغر داخل نطاق أكبر، سيتمكن النطاق الأصغر دائمًا من رؤية ما هو موجود في النطاق الأكبر.

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

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

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

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

مثال:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

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

الإغلاق هو دالة لديها حق الوصول إلى نطاق وظيفة أخرى (متغيراتها ووظائفها).أسهل طريقة لإنشاء إغلاق هي باستخدام دالة داخل دالة؛والسبب هو أن الوظيفة في JavaScript تتمتع دائمًا بإمكانية الوصول إلى نطاق الوظيفة التي تحتوي عليها.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

يُحذًِر:قرد

في المثال أعلاه، يتم استدعاء الدالة الخارجية والتي بدورها تستدعي الدالة الداخلية.لاحظ كيف يكون ExternalVar متاحًا لـInnerFunction، وهو ما يتضح من خلال تنبيه قيمة ExternalVar بشكل صحيح.

الآن فكر في ما يلي:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

يُحذًِر:قرد

تم تعيين ReferenceToInnerFunction على ExternalFunction()، والذي يُرجع ببساطة مرجعًا إلى InternalFunction.عندما يتم استدعاء ReferenceToInnerFunction، فإنه يُرجع ExternalVar.مرة أخرى، كما هو مذكور أعلاه، يوضح هذا أن InnerFunction لديه حق الوصول إلى ExternalVar، وهو متغير من ExternalFunction.علاوة على ذلك، من المثير للاهتمام ملاحظة أنه يحتفظ بهذا الوصول حتى بعد انتهاء تنفيذ ExternalFunction.

وهنا تصبح الأمور مثيرة للاهتمام حقًا.إذا أردنا التخلص من ExternalFunction، فلنفترض أنه تم تعيينها على قيمة خالية، فقد تعتقد أن الإشارة إلى InnerFunction ستفقد إمكانية الوصول إلى قيمة ExternalVar.ولكن هذا ليس هو الحال.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

يُحذًِر:تنبيه القرد:قرد

ولكن كيف هذا؟كيف يمكن أن يظل ReferenceToInnerFunction يعرف قيمة ExternalVar الآن بعد أن تم تعيين ExternalFunction على قيمة خالية؟

السبب وراء استمرار قدرة ReferenceToInnerFunction في الوصول إلى قيمة ExternalVar هو أنه عندما تم إنشاء الإغلاق لأول مرة عن طريق وضع InternalFunction داخل ExternalFunction، أضافت InternalFunction مرجعًا إلى نطاق ExternalFunction (متغيراته ووظائفه) إلى سلسلة نطاقه.ما يعنيه هذا هو أن InnerFunction لديه مؤشر أو مرجع لجميع متغيرات ExternalFunction، بما في ذلك ExternalVar.لذا، حتى عند انتهاء تنفيذ ExternalFunction، أو حتى إذا تم حذفها أو تعيينها على قيمة خالية، فإن المتغيرات الموجودة في نطاقها، مثل ExternalVar، تظل موجودة في الذاكرة بسبب المرجع المتميز لها من جانب الدالة الداخلية التي تم إرجاعها إليها مرجع إلى InnerFunction.لتحرير ExternalVar وبقية متغيرات ExternalFunction من الذاكرة، سيتعين عليك التخلص من هذا المرجع المتميز لها، على سبيل المثال عن طريق تعيين ReferenceToInnerFunction على القيمة null أيضًا.

//////////

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

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

يُحذًِر:غوريلا

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

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

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top