الإغلاق في TypeScript
في هذه المقالة، سنشرح الإغلاق في TypeScript۔
YouTube Video
الإغلاق في TypeScript
ما هو الإغلاق؟
الإغلاق يشير إلى القدرة على الاحتفاظ بالمراجع إلى النطاق (البيئة) الذي تم تعريف الوظيفة فيه، حتى إذا تم استدعاء الوظيفة خارج هذا النطاق۔ فيما يلي، سيتم شرح الإغلاق بما في ذلك توضيحات النوع۔
ببساطة، يدمج الإغلاق بين وظيفة وبيئة المتغير حيث تم تعريف الوظيفة، مما يسمح بالوصول إلى تلك البيئة عند استدعاء الوظيفة۔
الآلية الأساسية للإغلاق
في TypeScript، عند تعريف وظيفة داخل وظيفة أخرى، يصبح واضحًا أن الوظيفة الداخلية يمكنها الوصول إلى متغيرات الوظيفة الخارجية۔ إليك مثالاً أساسيًا على الإغلاق مع توضيحات النوع۔
1function outerFunction(): () => void {
2 let outerVariable: string = "I am from outer function";
3
4 function innerFunction(): void {
5 // The inner function accesses the variable of the outer function
6 console.log(outerVariable);
7 }
8
9 return innerFunction;
10}
11
12const closure: () => void = outerFunction();
13closure(); // "I am from outer function"
- نوع الإرجاع لـ
outerFunction
هو() => void
، مما يشير إلى أنه يعيد وظيفة۔ - تم تحديد نوع
innerFunction
صراحة إلىvoid
، مما يشير إلى أنه لا يحتوي على قيمة إرجاع۔
استخدامات ومزايا الإغلاق
تغليف البيانات
فيما يلي مثال على الإغلاق مع توضيحات النوع لتغليف البيانات۔
1function createCounter(): () => number {
2 let count: number = 0;
3
4 return function (): number {
5 count += 1;
6 return count;
7 };
8}
9
10const counter: () => number = createCounter();
11console.log(counter()); // 1
12console.log(counter()); // 2
13console.log(counter()); // 3
- وظيفة
createCounter
تعيد وظيفة من النوع() => number
۔ - تم تعريف المتغير
count
كنوعnumber
ويتم التعامل معه داخل الإغلاق۔
وظائف ذات ترتيب أعلى
الإغلاق مفيد عند إنشاء وظائف ذات ترتيب أعلى۔ فيما يلي مثال على وظيفة ذات ترتيب أعلى مع توضيحات النوع الواضحة۔
1function createMultiplier(multiplier: number): (value: number) => number {
2 return function (value: number): number {
3 return value * multiplier;
4 };
5}
6
7const double: (value: number) => number = createMultiplier(2);
8console.log(double(5)); // 10
9
10const triple: (value: number) => number = createMultiplier(3);
11console.log(triple(5)); // 15
createMultiplier
تأخذ معاملًا من النوعnumber
وتعيد وظيفة من النوع(value: number) => number
۔- الوظيفة الداخلية أيضًا تقبل
value
كنوعnumber
وتعيد النتيجة كنوعnumber
۔
مثال على تنفيذ الإغلاق في TypeScript
تنفيذ عداد محدود النطاق كإغلاق مع توضيحات النوع المضافة۔
1function rangeCounter(min: number, max: number): () => number | string {
2 let count: number = min;
3
4 return function (): number | string {
5 if (count <= max) {
6 return count++;
7 } else {
8 return `Count has exceeded the maximum value: ${max}`;
9 }
10 };
11}
12
13const counter: () => number | string = rangeCounter(1, 5);
14
15console.log(counter()); // 1
16console.log(counter()); // 2
17console.log(counter()); // 3
18console.log(counter()); // 4
19console.log(counter()); // 5
20console.log(counter()); // "Count has exceeded the maximum value: 5"
- وظيفة
rangeCounter
تعيد وظيفة تعيد إماnumber
أوstring
۔ - في الدالة الداخلية، إذا تجاوز
count
القيمةmax
، فإنه يُرجع رسالة من نوعstring
؛ خلاف ذلك، يُرجع قيمة من نوعnumber
۔
الاحتياطات عند استخدام الإغلاقات
تسربات محتملة للذاكرة بسبب الإغلاقات
يمكن للإغلاقات الاحتفاظ بالمتغيرات من النطاق الخارجي، مما قد يتسبب أحيانًا في تسرب للذاكرة۔ يجب تحرير الإغلاقات غير الضرورية من الذاكرة بشكل صريح۔
1function createLeak(): () => void {
2 // Large array consuming significant memory
3 const largeArray: string[] = new Array(1000000).fill("leak");
4
5 // Closure capturing `largeArray`
6 return function (): void {
7 console.log(largeArray[0]); // Using the captured array
8 };
9}
10
11// Create a closure that holds a reference to the large array
12let leakyFunction = createLeak();
13
14// The large array is not released as `leakyFunction` still references it
15
16// When the object is no longer needed
17leakyFunction = null; // Explicitly remove the reference
- في هذا الكود، من المفترض أن يتم تحرير
largeArray
التي تم إنشاؤها داخلcreateLeak
عندما تخرج عن النطاق، ولكن هذا لا يحدث لأن الإغلاق يحتفظ بـlargeArray
.۔ طالما أنleakyFunction
موجودة، ستستمر هذه الذاكرة غير الضرورية في الاحتفاظ بها.۔ - عندما يصبح الكائن أو المتغير غير ضروري، فإن تعيين مرجعه إلى
null
يسمح لجامع القمامة باكتشافه وتحرير الذاكرة.۔
سوء استخدام الإغلاقات في الحلقات
عند إنشاء إغلاقات داخل حلقة، قد تكون هناك مشكلات في الإشارة إلى نفس المتغير۔ المثال التالي يوضح حالة حيث لا يعمل المتغير i
بشكل صحيح۔
1for (var i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 3, 3, 3
لا يقدم هذا الكود النتيجة المطلوبة لأن i
يشير إلى القيمة 3 في نهاية الحلقة۔ لإصلاح ذلك، يمكن استخدام let
لفصل النطاق أو استخدام دالة تُستدعى فورًا۔
1for (let i: number = 0; i < 3; i++) {
2 setTimeout((): void => {
3 console.log(i);
4 }, 1000);
5}
6// Output: 0, 1, 2
باستخدام let
، يتم فصل نطاق i
لكل دورة في الحلقة، مما يعطي النتائج المتوقعة۔
الملخص
في TypeScript، يمكن أن تؤدي الإغلاقات إلى كود أكثر أمانًا وقابلية للتوقع من خلال الاستفادة من نظام النوعية۔ الاستخدام الصحيح للإغلاقات يسمح بتغليف البيانات وتصميم مرن للدوال ذات الترتيب الأعلى۔ بالإضافة إلى ذلك، يجب توخي الحذر مع إدارة الذاكرة والإشارات غير المقصودة للنطاق عند استخدام الإغلاقات۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔