كيف تعمل إغلاق JavaScript؟




function variables (20)

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

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

https://code.i-harness.com


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

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

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

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

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

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

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"

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


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

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

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

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

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

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

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

ملخصان موجزان

  • عندما تعلن دالة (foo) عن وظائف أخرى (bar و baz) ، فإن عائلة المتغيرات المحلية التي يتم إنشاؤها في foo لا يتم إتلافها عند خروج الدالة. المتغيرات تصبح مجرد غير مرئية للعالم الخارجي. وبالتالي يمكن لـ 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 ) في التعليمة البرمجية أعلاه. إذا لم تقم بذلك ، فأنت بحاجة إلى النظر في ذلك قبل أن تتعلم الإغلاق. say2 يستخدم C قد يفكر في الدالة على أنها say2 مؤشر إلى دالة ، وأن المتغيرات say و say2 كانت كل مؤشر إلى دالة.

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

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

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

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

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

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

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

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

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

مثال 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() مرة أخرى ، فسيتم إنشاء إغلاق جديد (إطار رصة!). يتم الكتابة فوق المتغيرات gSetNumber و gIncreaseNumber و gLogNumber القديمة gLogNumber جديدة لها الإغلاق الجديد. (في 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])} السطر result.push( function() {console.log(item + ' ' + list[i])} إشارة إلى وظيفة مجهولة ثلاث مرات إلى صفيف result.push( function() {console.log(item + ' ' + list[i])} لم تكن على دراية بالوظائف المجهولة ففكر في انها مثل:

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

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

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

الروابط

شكر

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

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


هل يمكن أن توضح عمليات الإغلاق لعمر 5 سنوات؟ *

ما زلت أعتقد أن تفسير Google يعمل جيدًا وموجزًا:

/*
*    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);​

* سؤال # AC


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

المثال البسيط التالي يغطي جميع النقاط الرئيسية لإغلاق 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العودة؟ الإجابة هي تخيل أن جافا سكريبت لا تستخدم "إطارات مكدس" ، ولكن بدلاً من ذلك تستخدم "إطارات كومة الذاكرة المؤقتة" ، والتي يمكن أن تستمر بعد استدعاء الدالة الذي جعلها ترجع.

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

هذا إلى حد كبير كل ما هو هناك لإغلاق.


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

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


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

    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.


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

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 ، يتم إنشاء سياق تنفيذ جديد. مع وسيطات الدالة والكائن الرئيسي ، يتلقى سياق التنفيذ أيضًا جميع المتغيرات المعلنة خارجها (في المثال أعلاه ، كلاهما "أ" و "ب").

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


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

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

ويكيبيديا حول الإغلاق :

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

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

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

نظم الإدارة البيئية

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


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

// 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

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

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

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

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

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


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

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

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

outerFunction();

تنبيه: قرد

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

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

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

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

تنبيه: قرد

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

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

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

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

outerFunction = null;
alert(referenceToInnerFunction());

تنبيه: قرد ALERT: قرد

لكن كيف هذا؟ كيف يمكن أن تشير الدالة TInInner إلى قيمة outerVar الآن بعد تعيين الدالة الخارجية على null؟

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

//////////

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

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

    innerFunction();
}

outerFunction();

تنبيه: الغوريلا

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


كيف سأشرح ذلك لعمر ست سنوات:

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


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

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

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

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

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

تخيل أن يتم تنفيذ أحداث زر Vote-Up و Vote-Down كـ closures ، voteUp_click and voteDown_click ، ​​التي تتمتع بإمكانية الوصول إلى المتغيرات الخارجية هي VotesU و VOWN Down ، والتي يتم تحديدها عالميًا. (من أجل التبسيط ، أنا أشير إلى أزرار تصويت الأسئلة الخاصة بـ ، وليس صف أزرار رد التصويت.)

عندما ينقر المستخدم على الزر VoteUp ، تقوم الدالة VoteUp_click بالتحقق مما إذا كان isVotedDown == true لتحديد ما إذا كنت ستصوت أو تلغي مجرد إجراء تصويت لأسفل. يعد function voteUp_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
}

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


A إغلاق يشبه إلى حد كبير كائن. يتم إنشاء مثيل كلما قمت باستدعاء دالة.

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

ويرد متغير في الإغلاق إذا كنت

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

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

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

مثال

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

أنت تنام و تدع دان أنت تخبر دان بإحضار وحدة تحكم 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.");

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

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


حسنا ، مروحة الإغلاق عمرها 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 تفاعلي لشرح كيفية عمل الإغلاقات. ما هو الإغلاق؟

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

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

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

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

وبهذا المعنى ، فإنهم يجعلون دالة تعمل قليلاً مثل كائن ذي سمات خاصة.

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

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


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

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

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

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

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

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

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

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

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

لذلك ، يمكن أن يكون للوظيفة g إمكانية الوصول إلى g و a و f.

إغلاق

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

ترميز LexicalEnvironments:

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

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

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

كي تختصر:

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

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





closures