المقدمة

هنتكلم النهاردة عن مفهوم من أهم المفاهيم في الـ JavaScript وهو الـ Closure وهو ببساطة قدرة الـ Functions الداخلية على الوصول للـ Variables المعرفة في نطاق الـ Function الخارجية حتى بعد انتهاء الـ Function الخارجية من التنفيذ.


فرصة تنافس وتطور مهاراتك وتكسب 2500 جنيه! 🏆

فرصة جديدة ومسابقة جديدة من CodeQuests 🚀

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

هتشتغل على الـ QA Task💻، هتنافس ناس تانية زيك 🤝، هتكتسب خبرة مهمة 📚، تبني portfolio احترافي 💼، وكمان فيه جوائز قيمة للمراكز الأولى 🥇🥈🥉.🚨

متفوتش الفرصة! سجل دلوقتي من اللينك ده!


Lexical Scope vs Dynamic Scope

خلينا علشان نفهم ال closure بشكل أفضل نشرح ال lexical scope او ما يسمي بال static scope وهو طريقة بتحدد المتغيرات اللي تقدر كل function توصلها على حسب مكان ووقت كتابة ال function وليس مكان استدعائها.

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

// example 1
// Global Scope
const globalVariable = "I am global";

function outerFunction() {
  // Outer Function Scope
  const outerVariable = "I am from the outer scope";

  function innerFunction() {
    // Inner Function Scope
    const innerVariable = "I am from the inner scope";

    // Accessing variables from all scopes
    console.log(innerVariable); //output: I am from the inner scope
    console.log(outerVariable); // output: I am from the outer scope
    console.log(globalVariable); // output: I am global
  }

  innerFunction();

  // Trying to access variables from innerFunction
  console.log(innerVariable); // output: Error: innerVariable is not defined
}

outerFunction();

// Trying to access variables from outerFunction
console.log(outerVariable); // output: Error: outerVariable is not defined

هنلاقي هنا نقدر نوصل لل variables من الداخل إلى الخارج وليس العكس.


كما ذكرنا في lexical scope نطاق ال function بيتحدد وقت ومكان كتابة ال function في الكود وليس مكان استدعائها لكن في dynamic scope نطاق ال function بيتحدد وقت استدعاء ال function كما هو موضح في المثال:

// example 2
function outerFunction() {
  const outerVariable = "outer";

  innerFunction();
}
function innerFunction() {
  console.log(outerVariable);
}

outerFunction(); // output: Error: outerVariable is not defined
//IF THIS IS LEXICAL SCOPE THEN IT SHOULD WORK OUTPUT SHOULD BE "outer"

في المثال ده الناتج هيبقي ERROR بسبب lexical scoping إن ال function اللي اسمها ()innerFunction هتحاول تشوف المتغير outerVariable في نطاقها مش هتلاقيه ومش موجود في الـ global scope ولكن لو افترضنا إن الجافاسكريبت تستخدم dynamic scoping والذي بيحدد نطاق ال function مكان استدعائها وليس مكان كتابتها.

هنلاقي في الحالة ديه بما إن ال ()innerFunction تم استدعائها بداخل ()outerFunction هيكون لها القدرة على الوصول لنطاق ال outerFunction وبالتالي الوصول لقيمة المتغير outerVariable والناتج هيكون "outer". 


Closure

دلوقتي بعد ماعرفنا lexical scoping خلينا نرجع لمفهوم ال closure وهو انه يتم إنشاؤه لما الدالة الداخلية تكون بتستخدم أحد متغيرات ال function الخارجية حتى بعد انتهاء تنفيذ الدالة الخارجية كما هو موضح في المثال:

// example 3
function createCounter() {
  let count = 0;
  return function () {
    count++;
    console.log(count);
  };
}

const counter = createCounter();
counter(); // output: 1
counter(); // output: 2
counter(); // output: 3 

هنلاقي هنا إن قيمة ال count بتزيد في كل مرة بالرغم إن createCounter تم تنفيذها وانتهت ولكن بسبب ال closure لل function الداخلية قدرت انها تتذكر قيمة ال count بالرغم من انتهاء تنفيذ ال function الخارجية.

ملحوظة في الطبيعي الجافاسكريبت تقوم بمسح ال variables التي لم تعد تستخدم ولكن اذا تم استخدامها في مكان اخر كـ function داخلية كما رأينا في المثال السابق هنا يتم انشاء ال closure لعمل اتصال بينها وبين قيمة ال variable وده ممكن نشوفه عن طريق console.dir ونشوف ال [[scopes]]  الخاص بال function  كما هو موضح في الأمثلة:

// example 4
const printMyName = () => {
  const name = "John Doe";

  const print = () => {
    console.log("hello");
  };

  console.dir(print); // [[scopes]] -> not found closures
};

printMyName();
Closure in JavaScript

في المثال ده الـ function اللي اسمها print مش بتستخدم اي variable خارجي عن نطاقها لذلك مش هيتم انشاء closure لكن في المثال ده:

example 5
const printMyName = () => {
  const name = "John Doe";

  const print = () => {
    console.log(name);
  };

  console.dir(print); // [[scopes]] -> closure -> {name: "John Doe"}
};

printMyName();
Closure in JavaScript

هنا بسبب إن الدالة الداخلية بتستخدم متغير من الدالة الخارجية تم انشاء closure لتتذكر دائما قيمة المتغير الخارجي حتى بعد انتهاء تنفيذ الدالة الخارجية.

مثال تاني للتوضيح أكثر

let f;
const g = function () {

  const a = 23;

  f = function () {
    console.log(a * 2);
  };
};

g();

f(); // output: 46
console.dir(f); // [[scopes]] -> closure -> {a: 23}
Closure in JavaScript

هنلاقي هنا تم انشاء closure في ال function اللي اسمها f بقيمة ال a بالرغم من انتهاء تنفيذ ال function اللي اسمها g. 

طيب ايه اللي هيحصل لو عملنا reassing لدالة f في دالة تانية بقيمة variable مختلف زي المثال ده:

// example 7

let f;

const g = function () {
  const a = 23;
  f = function () {
    console.log(a * 2);
  };
};

const h = function () {
  const b = 500;
  f = function () {
    console.log(b * 2);
  };
};


g();
f(); // output: 46
console.dir(f); // [[scopes]] -> closure -> {a: 23}

// re-assigning f function

h();
f(); // output: 1000
console.dir(f); // [[scopes]] -> closure -> {b: 500}

هنلاقي إن ال closure اتغيرت بتغيير قيمة f وده بيوضح إن ال closure يتأكد دائما إن ال function تبقي تتذكر أو على اتصال مع ال variables اللي موجودة مكان كتابة ال function.


في الختام

كده نكون وصلنا لنهاية المقالة، واتمنى تكونوا فهمتم مفهوم الـ Closure بشكل واضح، لأنه فعلاً من أهم المفاهيم في ال javascript و يفتح لك الباب لفهم أعمق لكيفية عمل اللغة وطريقة التعامل مع ال variables وال scopes.

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