استكشف إغلاقات JavaScript من خلال أمثلة عملية، وفهم كيفية عملها وتطبيقاتها الواقعية في تطوير البرمجيات.
إغلاقات JavaScript: تبسيطها بأمثلة عملية
الإغلاقات هي مفهوم أساسي في JavaScript غالبًا ما يسبب الارتباك للمطورين من جميع المستويات. فهم الإغلاقات أمر بالغ الأهمية لكتابة التعليمات البرمجية بكفاءة وقابلة للصيانة وآمنة. سيرشدك هذا الدليل الشامل إلى تبسيط الإغلاقات بأمثلة عملية وتوضيح تطبيقاتها الواقعية.
ما هو الإغلاق؟
ببساطة، الإغلاق هو مزيج من دالة والبيئة المعجمية التي تم الإعلان عن تلك الدالة فيها. هذا يعني أن الإغلاق يسمح للدالة بالوصول إلى المتغيرات من نطاقها المحيط، حتى بعد انتهاء الدالة الخارجية من التنفيذ. فكر في الأمر على أنه الدالة الداخلية "تتذكر" بيئتها.
لفهم هذا حقًا، دعنا نحلل المكونات الرئيسية:
- الدالة: الدالة الداخلية التي تشكل جزءًا من الإغلاق.
- البيئة المعجمية: النطاق المحيط حيث تم الإعلان عن الدالة. يتضمن ذلك المتغيرات والدوال والإعلانات الأخرى.
يحدث السحر لأن الدالة الداخلية تحتفظ بالوصول إلى المتغيرات في نطاقها المعجمي، حتى بعد عودة الدالة الخارجية. هذا السلوك هو جزء أساسي من كيفية تعامل JavaScript مع النطاق وإدارة الذاكرة.
لماذا الإغلاقات مهمة؟
الإغلاقات ليست مجرد مفهوم نظري؛ إنها ضرورية للعديد من أنماط البرمجة الشائعة في JavaScript. أنها توفر الفوائد التالية:
- تغليف البيانات: تسمح لك الإغلاقات بإنشاء متغيرات وطرق خاصة، وحماية البيانات من الوصول والتعديل الخارجي.
- الحفاظ على الحالة: تحتفظ الإغلاقات بحالة المتغيرات بين استدعاءات الدالة، وهو أمر مفيد لإنشاء العدادات والمؤقتات والمكونات الأخرى ذات الحالة.
- الدوال ذات الترتيب الأعلى: غالبًا ما تستخدم الإغلاقات بالاشتراك مع الدوال ذات الترتيب الأعلى (الدوال التي تأخذ دوال أخرى كوسائط أو ترجع دوال)، مما يتيح تعليمات برمجية قوية ومرنة.
- JavaScript غير المتزامن: تلعب الإغلاقات دورًا حاسمًا في إدارة العمليات غير المتزامنة، مثل عمليات الاسترجاع والوعود.
أمثلة عملية لإغلاقات JavaScript
دعنا نتعمق في بعض الأمثلة العملية لتوضيح كيفية عمل الإغلاقات وكيف يمكن استخدامها في سيناريوهات واقعية.
مثال 1: عداد بسيط
يوضح هذا المثال كيف يمكن استخدام الإغلاق لإنشاء عداد يحتفظ بحالته بين استدعاءات الدالة.
function createCounter() {
let count = 0;
return function() {
count++;
console.log(count);
};
}
const increment = createCounter();
increment(); // Output: 1
increment(); // Output: 2
increment(); // Output: 3
شرح:
createCounter()
هي دالة خارجية تعلن عن متغيرcount
.- تقوم بإرجاع دالة داخلية (دالة مجهولة في هذه الحالة) تزيد
count
وتسجل قيمتها. - تشكل الدالة الداخلية إغلاقًا على المتغير
count
. - حتى بعد انتهاء
createCounter()
من التنفيذ، تحتفظ الدالة الداخلية بالوصول إلى المتغيرcount
. - يزيد كل استدعاء لـ
increment()
نفس المتغيرcount
، مما يدل على قدرة الإغلاق على الحفاظ على الحالة.
مثال 2: تغليف البيانات بمتغيرات خاصة
يمكن استخدام الإغلاقات لإنشاء متغيرات خاصة، وحماية البيانات من الوصول المباشر والتعديل من خارج الدالة.
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: function(amount) {
balance += amount;
return balance; //Returning for demonstration, could be void
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
return balance; //Returning for demonstration, could be void
} else {
return "Insufficient funds.";
}
},
getBalance: function() {
return balance;
}
};
}
const account = createBankAccount(1000);
console.log(account.deposit(500)); // Output: 1500
console.log(account.withdraw(200)); // Output: 1300
console.log(account.getBalance()); // Output: 1300
// Trying to access balance directly will not work
// console.log(account.balance); // Output: undefined
شرح:
createBankAccount()
ينشئ كائن حساب مصرفي بطرق للإيداع والسحب والحصول على الرصيد.- يتم الإعلان عن المتغير
balance
ضمن نطاقcreateBankAccount()
ولا يمكن الوصول إليه مباشرة من الخارج. - تشكل الطرق
deposit
وwithdraw
وgetBalance
إغلاقات على المتغيرbalance
. - يمكن لهذه الطرق الوصول إلى المتغير
balance
وتعديله، ولكن المتغير نفسه يظل خاصًا.
مثال 3: استخدام الإغلاقات مع `setTimeout` في حلقة
الإغلاقات ضرورية عند العمل مع العمليات غير المتزامنة، مثل setTimeout
، خاصة داخل الحلقات. بدون إغلاقات، قد تواجه سلوكًا غير متوقع بسبب الطبيعة غير المتزامنة لـ JavaScript.
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function() {
console.log("Value of i: " + j);
}, j * 1000);
})(i);
}
// Output:
// Value of i: 1 (after 1 second)
// Value of i: 2 (after 2 seconds)
// Value of i: 3 (after 3 seconds)
// Value of i: 4 (after 4 seconds)
// Value of i: 5 (after 5 seconds)
شرح:
- بدون الإغلاق (تعبير الدالة الذي يتم استدعاؤه فورًا أو IIFE)، ستشير جميع عمليات الاسترجاع
setTimeout
في النهاية إلى نفس المتغيرi
، الذي سيكون له قيمة نهائية قدرها 6 بعد اكتمال الحلقة. - ينشئ IIFE نطاقًا جديدًا لكل تكرار للحلقة، ويلتقط القيمة الحالية لـ
i
في المعاملj
. - يشكل كل استرجاع
setTimeout
إغلاقًا على المتغيرj
، مما يضمن تسجيل القيمة الصحيحة لـi
لكل تكرار.
سيؤدي استخدام let
بدلاً من var
في الحلقة أيضًا إلى إصلاح هذه المشكلة، حيث ينشئ let
نطاقًا كتلة لكل تكرار.
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log("Value of i: " + i);
}, i * 1000);
}
// Output (same as above):
// Value of i: 1 (after 1 second)
// Value of i: 2 (after 2 seconds)
// Value of i: 3 (after 3 seconds)
// Value of i: 4 (after 4 seconds)
// Value of i: 5 (after 5 seconds)
مثال 4: الكاري والتطبيق الجزئي
تعتبر الإغلاقات أساسية للكاري والتطبيق الجزئي، وهي تقنيات تستخدم لتحويل الدوال ذات الوسائط المتعددة إلى تسلسلات من الدوال التي تأخذ كل منها وسيطة واحدة.
function multiply(a) {
return function(b) {
return function(c) {
return a * b * c;
};
};
}
const multiplyBy5 = multiply(5);
const multiplyBy5And2 = multiplyBy5(2);
console.log(multiplyBy5And2(3)); // Output: 30 (5 * 2 * 3)
شرح:
multiply
هي دالة كارية تأخذ ثلاث وسائط، واحدة في كل مرة.- تشكل كل دالة داخلية إغلاقًا على المتغيرات من نطاقها الخارجي (
a
،b
). multiplyBy5
هي دالة تم بالفعل تعيينa
لها على 5.multiplyBy5And2
هي دالة تم بالفعل تعيينa
لها على 5 وb
على 2.- يكمل الاستدعاء الأخير لـ
multiplyBy5And2(3)
الحساب ويعيد النتيجة.
مثال 5: نمط الوحدة
تستخدم الإغلاقات بشكل كبير في نمط الوحدة، مما يساعد في تنظيم وهيكلة كود JavaScript، وتعزيز النمطية ومنع تعارضات التسمية.
const myModule = (function() {
let privateVariable = "Hello, world!";
function privateMethod() {
console.log(privateVariable);
}
return {
publicMethod: function() {
privateMethod();
},
publicProperty: "This is a public property."
};
})();
console.log(myModule.publicProperty); // Output: This is a public property.
myModule.publicMethod(); // Output: Hello, world!
// Trying to access privateVariable or privateMethod directly will not work
// console.log(myModule.privateVariable); // Output: undefined
// myModule.privateMethod(); // Output: TypeError: myModule.privateMethod is not a function
شرح:
- ينشئ IIFE نطاقًا جديدًا، ويغلف
privateVariable
وprivateMethod
. - يكشف الكائن الذي تم إرجاعه فقط
publicMethod
وpublicProperty
. - يشكل
publicMethod
إغلاقًا علىprivateMethod
وprivateVariable
، مما يسمح له بالوصول إليهما حتى بعد تنفيذ IIFE. - ينشئ هذا النمط وحدة نمطية بشكل فعال مع أعضاء خاصين وعامين.
الإغلاقات وإدارة الذاكرة
في حين أن الإغلاقات قوية، فمن المهم أن تكون على دراية بتأثيرها المحتمل على إدارة الذاكرة. نظرًا لأن الإغلاقات تحتفظ بالوصول إلى المتغيرات من نطاقها المحيط، فإنها يمكن أن تمنع جمع القمامة لتلك المتغيرات إذا لم تعد هناك حاجة إليها. يمكن أن يؤدي ذلك إلى تسرب الذاكرة إذا لم يتم التعامل معه بعناية.
لتجنب تسرب الذاكرة، تأكد من كسر أي مراجع غير ضرورية للمتغيرات داخل الإغلاقات عندما لم تعد هناك حاجة إليها. يمكن القيام بذلك عن طريق تعيين المتغيرات على null
أو عن طريق إعادة هيكلة التعليمات البرمجية الخاصة بك لتجنب إنشاء إغلاقات غير ضرورية.
أخطاء الإغلاق الشائعة التي يجب تجنبها
- نسيان النطاق المعجمي: تذكر دائمًا أن الإغلاق يلتقط البيئة *في وقت إنشائه*. إذا تغيرت المتغيرات بعد إنشاء الإغلاق، فسوف يعكس الإغلاق هذه التغييرات.
- إنشاء إغلاقات غير ضرورية: تجنب إنشاء إغلاقات إذا لم تكن هناك حاجة إليها، لأنها يمكن أن تؤثر على الأداء واستخدام الذاكرة.
- تسريب المتغيرات: كن على دراية بعمر المتغيرات التي تم التقاطها بواسطة الإغلاقات وتأكد من تحريرها عندما لم تعد هناك حاجة إليها لمنع تسرب الذاكرة.
استنتاج
تعتبر إغلاقات JavaScript مفهومًا قويًا وأساسيًا يجب على أي مطور JavaScript فهمه. أنها تتيح تغليف البيانات والحفاظ على الحالة والدوال ذات الترتيب الأعلى والبرمجة غير المتزامنة. من خلال فهم كيفية عمل الإغلاقات وكيفية استخدامها بفعالية، يمكنك كتابة تعليمات برمجية أكثر كفاءة وقابلة للصيانة وأمانًا.
قدم هذا الدليل نظرة عامة شاملة على الإغلاقات مع أمثلة عملية. من خلال التدريب والتجربة مع هذه الأمثلة، يمكنك تعميق فهمك للإغلاقات وتصبح مطور JavaScript أكثر كفاءة.
مزيد من التعلم
- شبكة مطوري Mozilla (MDN): الإغلاقات - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
- أنت لا تعرف JS: النطاق والإغلاقات بقلم كايل سيمبسون
- استكشف منصات الترميز عبر الإنترنت مثل CodePen و JSFiddle لتجربة أمثلة إغلاق مختلفة.