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