نحتاج في بعض الأحيان لتكرار إجراء ما مثل إخراج قيم من قائمة واحدة تلو الأخرى أو تشغيل نفس الشيفرة للأعداد من 1 إلى 10 لكل عدد على حدة.
حلقات التكرار (loops) عبارة عن وسيلة لتكرار شيفرة ما عدة مرات.
حلقة التكرار while
الصيغة الخاصة بها:
while (condition) { // الشيفرة المراد تكرار تنفيذها // تدعى جسم الحلقة }
طالما كان الشرط condition
مُحققًا، أي true
، تُنفذ الشيفرة الموجودة في جسم الحلقة. على سبيل المثال، تطبع حلقة التكرار أدناه قيمة المتغير i
طالما كان الشرط i < 3
مُحققًا:
let i = 0; while (i < 3) { // إظهار 0 ثم 1 ثم 2 alert( i ); i++; }
يُسمى التنفيذ الواحد من جسم حلقة التكرار «تكرارًا» (iteration). ينفِّذ المثال السابق ثلاثة تكرارات. إذا كان i++
غير موجود في المثال أعلاه، ستُكرَّر الحلقة (نظريًا) إلى اللانهاية. أمَّا عمليًا، يوقف المتصفح تكرار مثل هذه الحلقات اللانهائية عند حدٍّ معيَّن، ويمكنك إنهاء العملية أيضًا من طرف الخادم في JavaScript.
شرط حلقة التكرار غير مقصور على تعبيرات الموازنة، بل يمكن أن يكون أي تعبير أو متغير: يُقّيم الشرط ويُحوّل إلى قيمة منطقية بواسطة حلقة التكرار while
.
على سبيل المثال، الطريقة الأقصر لكتابة while (i != 0)
هي while (i)
:
let i = 3; while (i) { // وتتوقف الحلقة false صفرًا 0، يُقيَّم إلى القيمة i عندما تصبح قيمة المتغير alert( i ); i--; }
ليس هناك حاجة للأقواس المعقوصة عند كتابة سطر برمجي واحد
إذا كان جسم حلقة التكرار عبارة عن سطر واحد، يمكنك حذف الاقواس المعقوصة {…}
:
let i = 3; while (i) alert(i--);
حلقة التكرار do..while
يمكن إزاحة شرط حلقة التكرار الى أسفل جسم الحلقة باستخدام الصيغة do..while
:
do { // جسم الحلقة } while (condition);
ستُنفذ أولًا الشيفرة الموجودة في جسم الحلقة ثم يتم التحقق من الشرط؛ فإذا كان الشرط مُحققًا، تُكرَّر هذه العملية مرة أخرى. إليك المثال التالي:
let i = 0; do { alert( i ); i++; } while (i < 3);
تستخدم هذه الصيغة عندما ترغب في تنفيذ الشيفرة مرة واحدة على الأقل بغض النظر عن كون الشرط محققًا أم لا. عادة ما تُفضل الصيغة الأخرى: while(…) {…}
.
حلقة التكرار for
تعد حلقة التكرار for
أكثر حلقات التكرار شيوعًا. تبدو صياغة الحلقة بالشكل التالي:
for (begin; condition; step) { // ... جسم الحلقة ... }
إليك المثال التالي لتتعرف ماهية هذه الأجزاء begin; condition; step
. تُنفِّذ حلقة التكرار أدناه الدالة alert(i)
لقيمة المتغيِّر i
العددية من 0
إلى 3
( باستثناء العدد 3، لأن الشرط i < 3
لا يشمل العدد 3):
for (let i = 0; i < 3; i++) { // إظهار 0 ثم 1 ثم 2 alert(i); }
يعرض الجدول التالي شرحًا مفصلًا لأجزاء حلقة التكرار السابقة:
الجزء | الشيفرة المقابلة | الوظيفة |
---|---|---|
البدء (التهيئة) |
i = 0
|
يُنفَّذ مرةً واحدةً لحظة ولوج الحلقة |
الشرط |
i < 3
|
يُتحقَّق من هذا الشرط قبل كل تكرار (دورة تنفيذ) للحلقة، فإذا كان غير محقَّق (false )، يوقف تنفيذ الحلقة.
|
الجسم |
alert(i)
|
يُنفَّذ ما دام الشرط محقَّقًا (true ).
|
الخطوة |
i++
|
يُنفَّذ بعد تنفيذ جسم الحلقة في كل تكرار. |
تعمل خوارزمية حلقة التكرار كالتالي:
-
دخول الحلقة:
- -> إذا تحقَّق الشرط -> نفِّذ جسم الحلقة ثمَّ نفِّذ الخطوة
- -> إذا تحقَّق الشرط -> نفِّذ جسم الحلقة ثمَّ نفِّذ الخطوة
- -> إذا تحقَّق الشرط -> نفِّذ جسم الحلقة ثمَّ نفِّذ الخطوة
- -> …
في حال كنت مبتدئًا، قد يساعدك ذلك في العودة إلى المثال وتدوين آلية تنفيذه خطوة بخطوة على الورق. إليك شرح لما يحدث في هذه الحالة:
// for (let i = 0; i < 3; i++) alert(i) // دخول الحلقة let i = 0 // إذا تحقَّق الشرط -> نفِّذ جسم الحلقة ثمَّ نفِّذ الخطوة if (i < 3) { alert(i); i++ } // إذا تحقَّق الشرط -> نفِّذ جسم الحلقة ثمَّ نفِّذ الخطوة if (i < 3) { alert(i); i++ } // إذا تحقَّق الشرط -> نفِّذ جسم الحلقة ثمَّ نفِّذ الخطوة if (i < 3) { alert(i); i++ } // i == 3 أوقف الحلقة لعدم تحقُّق الشرط 3 > 3 عند
التصريح عن المتغيرات داخل نطاق الحلقة
يُصرَّح عن المتغير i
المسمى «بالعدَّاد» مباشرةً ضمن حلقة التكرار، لذا تكون متاحةً ضمن نطاقها فقط وغير ظاهرة للعموم.
for (let i = 0; i < 3; i++) { alert(i); // 0, 1, 2 } alert(i); // خطأ! لا يوجد متغيِّر بهذا الاسم
بدلًا من تعريف متغير جديد، يمكنك استخدام متغير معرَّف مسبقًا:
let i = 0; for (i = 0; i < 3; i++) { // استعمال متغير موجود مسبقًا alert(i); // 0, 1, 2 } alert(i); // يعرض القيمة 3 لوجوده ضمن النطاق العام
تجاهل بعض أجزاء التحكم بحلقة التكرار for
يمكن تجاهل أي جزء من أجزاء الحلقة begin; condition; step
مثل حذف جزء التهيئة begin
في حال لم يكن وجوده مهمًا كما في المثال التالي:
let i = 0; // المتغيِّر جاهز for (; i < 3; i++) { // فلا حاجة لقسم التهيئة alert( i ); // 0, 1, 2 }
يمكنك أيضًا حذف الخطوة step
:
let i = 0; for (; i < 3;) { alert( i++ ); }
فتصبح حينئذٍ الحلقة for
مطابقة للحلقة while (i < 3)
.
يمكنك فعليًا إزالة جميع الأجزاء، وخلق حلقة تكرار لانهائية:
for (;;) { // تكرار لا نهائي }
يرجى التحقق من وجود الفاصلتين المنقوطتين ;
في صيغة حلقة التكرار for
. خلاف ذلك، سيُطلَق خطأ.
إيقاف حلقة التكرار
يتوقف تنفيذ حلقة التكرار عندم عدم تحقُّق الشرط أي أصبح التقييم المنطقي للشرط false
. مع ذلك، يمكنك إيقاف تنفيذ الحلقة في أي وقت باستخدام التعليمة break
.
في المثال التالي، تطلب حلقة التكرار من المستخدم إدخال سلسلة من الأرقام عن طريق الدالة prompt
، ويتوقف تنفيذ الحلقة عندما لا يُدخَل أي رقم:
let sum = 0; while (true) { let value = +prompt("Enter a number", ''); if (!value) break; // (*) sum += value; } alert( 'Sum: ' + sum );
عند النظر إلى الشيفرة، تُنفَّذ الكلمة المفتاحية break
في السطر (*)
إذا أدخل المستخدم سطرًا فارغًا أو ألغى عملية الإدخال وبذلك تتوقف حلقة التكرار فورًا، وينتقل تنفيذ الشيفرة إلى السطر الأول بعد حلقة التكرار أي إلى الدالة alert
.
يمكن استعمال حلقة أبدية (لانهائية) مع الكلمة المفتاحية break
في الحالات التي لا يُعرَّف فيها متى يصبح الشرط غير محقَّقٍ.
الاستمرار في التكرار التالي
الموجِّه continue
هو "نسخة أخف" من الموجِّه break
، إذ لا يوقف تنفيذ حلقة التكرار بأكملها بل يوقف تنفيذ التكرار الحالي فقط، وينتقل لتنفيذ التكرار التالي (إذا تحقق الشرط طبعًا). أي نستعمله في الحالات التي نرغب فيها بإيقاف تنفيذ التكرار الحالي والانتقال إلى التكرار التالي.
حلقة التكرار أدناه تستخدم الموجِّه continue
لإخراج القيم الفردية فقط من الأعداد 0 وحتى 10:
for (let i = 0; i < 10; i++) { // إذا تحقق التعبير، تخطى جسم الحلقة وانتقل للتكرار التالي if (i % 2 == 0) continue; alert(i); // 1, 3, 5, 7, 9 }
فيما يخص القيم الزوجية i
، يوقف التعبير البرمجي continue
تنفيذ حلقة التكرار وينتقل إلى التكرار التالي في الحلقة for
(مع الرقم التالي)، لذلك تظهر الدالة alert
فقط القيم الفردية.
تقليل مستوى التداخل عبر الموجِّه continue
حلقة التكرار المسؤولة عن إخراج القيم الفردية سوف تبدو كما يلي:
for (let i = 0; i < 10; i++) { if (i % 2) { alert( i ); } }
من الناحية التقنية، هذا مشابه للمثال أعلاه. يمكنك استخدام الشرط if
بدلًا من استخدام الموجِّه continue
. ولكن سيؤدي ذلك لخلق مستوى إضافي من التداخل (استدعاء الدالة alert
داخل الأقواس المعقوصة {}
). تقل قابلية القراءة الإجمالية إذا كانت الشيفرة الموجودة داخل التعبير الشرطي if
أطول من بضعة أسطر.
لا يُستخدم الموجهان break/continue
في المعامل الشرطي الثلاثي ?
لاحظ أنه لا يمكن استخدام البنى التي لا تشبه صياغتها التعابير البرمجية مع المعامل الثلاثي ?
تحديدًا موجهات مثل break/continue
.
إليك الشيفرة التالية:
if (i > 5) { alert(i); } else { continue; }
أعد كتابتها باستخدام المعامل الشرطي ?
:
(i > 5) ? alert(i) : continue; // هنا continue لا يسمح باستخدام الموجه
توقفت الشيفرة عن العمل بسبب خطأ في الصياغة (syntax error)، وهذا سبب آخر لعدم استخدام المعامل ?
بدلًا من الشرط if
.
تسمية حلقات التكرار
تحتاج في بعض الأحيان لإيقاف حلقات تكرار متداخلة ومتعددة في وقت واحد. على سبيل المثال، تُنفذ حلقتي تكرار في الشيفرة التالية على المتغيرين i
و j
، لإخراج الإحداثيات (i, j)
من (0,0)
إلى (3,3)
:
for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let input = prompt(`Value at coords (${i},${j})`, ''); // التي تليها؟ alert ماذا لو أردت الخروج من الحلقة والانتقال للدالة } } alert('Done!');
ستحتاج إلى طريقة لإيقاف حلقة التكرار إذا ألغى المستخدم الإدخال.
استخدام الموجِّه break
بعد input
سيُوقف حلقة التكرار الداخلية فقط. هذا ليس كافيًا، فما الحل يا ترى؟! هنا يأتي دور اللافتات (labels)!
اللافتة (label) عبارة عن مُعرِّف (وليست كلمةً محجوزةً) يتبعه نقطتين رأسيتين وتأتي قبل حلقة التكرار مباشرةً:
labelName: for (...) { ... }
يوقف الموجِّه break <labelName>
في المثال أدناه تنفيذه حلقة التكرار ذات المُعرِّف <labelName>
(أي outer
في حالتنا):
outer: for (let i = 0; i < 3; i++) { for (let j = 0; j < 3; j++) { let input = prompt(`Value at coords (${i},${j})`, ''); // الخروج من كلتا الحلقتين إن ألغيت عملية الإدخال أو أعطيت قيم فارغة if (!input) break outer; // (*) // افعل شيئًا بالقيم المعطاة } } alert('Done!');
وظيفة الموجِّه break outer
في المثال السابق إيقاف حلقة تكرار مسماة بالاسم outer
؛ لذلك، ينتقل تنفيذ الشيفرة مباشرة من السطر (*)
إلى السطر alert('Done!')
.
يمكنك وضع اللافتة في سطر منفصل:
outer: for (let i = 0; i < 3; i++) { ... }
يُستخدَم الموجِّه continue
مع لافتة أيضًا وينتقل تنفيذ التعليمات البرمجية آنذاك إلى التكرار التالي ليس لحلقة التكرار الموجود فيها وإنما للحلقة الموسومة بتلك اللافتة.
لا تسمح اللافتات بالقفز إلى أي مكان في الشيفرة
لا تسمح لك اللافتات بالانتقال إلى أي سطر تريده في الشيفرة لتنفيذه إجباريًا، فمن المستحيل مثلًا تنفيذ المثال التالي:
break label; // label لا يمكن القفز إلى السطر التالي المعنون بـ label: for (...)
لا يمكن إستدعاء break/continue
إلا من داخل حلقة تكرارية ويمكن استعمال لافتة معهما إن سبق عنونة الحلقة بها مسبقًا.
الخلاصة
تعرفت إلى الآن على ثلاثة أنواع من حلقات التكرار:
-
while
: تتحقَّق من الشرط قبل كل تكرار، ويُنفذ التكرار إذا كان الشرط محققًا. -
do..while
: تتحقَّق من الشرط بعد كل تكرار، ولا يُنفذ التكرار التالي إذا لم يكن الشرط محققًا. -
for (;;)
: تتحقَّق من الشرط قبل كل تكرار، وهناك أيضًا عناصر تتيح التحكم أكثر بالحلقة.
لبناء حلقة أبدية (لا نهائية)، تُستخدم حلقة التكرار while(true)
(أي أن يكون الشرط دائمًا محققًا)، ويمكن إيقاف هذه الحلقة مثل أي حلقة أخرى، عن طريق الموجِّه break
.
في حال عدم رغبتك بتنفيذ التكرار الحالي وتود الانتقال إلى التكرار التالي، فيمكنك استخدام الموجِّه continue
. ويمكنك استخدام الموجين break/continue
مع لافتات (labels) شرط أن تكون مُعرّفة قبل بداية حلقة التكرار. فتوفر اللافتة عندما استعمالها مع break/continue
تحكمًا أكبر بالحلقات المتداخلة.
التمارين
قيمة حلقة التكرار الأخيرة
الأهمية: 3
ما هي القيمة الأخيرة التي أظهرتها الدالة alert
في هذه الشيفرة؟ ولماذا؟
let i = 3; while (i) { alert( i-- ); }
الحل
الإجابة: 1
.
let i = 3; while (i) { alert( i-- ); }
تَنقص قيمة المتغير i
بمقدار 1
في كل تكرار في حلقة التكرار هذه. تتوقف حلقة التكرار while(i)
عندما يتحقق الشرط i = 0
، وبالتالي تشكل خطوات حلقة التكرار هذه التسلسل التالي:
let i = 3; alert(i--); // بمقدار 1 لتصبح 2 i إظهار 3 ثم إنقاص قيمة alert(i--) // بمقدار 1 لتصبح 1 i إظهار 2 ثم إنقاص قيمة alert(i--) // بمقدار 1 لتصبح 0 i إظهار 1 ثم إنقاص قيمة // توقف الحلقة لعدم تحقق الشرط
ما هي القيم التي ستظهرها حلقة التكرار while
؟
الأهمية: 4
سجّل القيمة الناتجة من كل تكرار في الحلقة، ثم وازنها بالإجابة النهائية. هل تُظهر الدالة alert
نفس القيم في الحلقتين أم لا؟
-
النموذج السابق
i++
:
let i = 0; while (++i < 5) alert( i );
-
النموذج اللاحق
++i
:
let i = 0; while (i++ < 5) alert( i );
الحل
يوضح هذا التمرين كيف يمكن أن يؤدي النموذج السابق/ اللاحق (postfix/prefix) إلى نتائج مختلفة عند استخدامهما في الموازنات.
- من 1 إلى 4
let i = 0; while (++i < 5) alert( i );
القيمة الأولى هي i = 1
، لأن معامل الزيادة i++
الأول يزيد i
ثم يُرجع القيمة الجديدة. لذلك الموازنة الأولى هي1 < 5
وتُظهر الدالة alert
العدد 1
.
ثم تتبعها الأعداد 2، 3، 4. تستخدم الموازنة دائمًا القيمة الأخيرة، نظرًا لأن معامل الزيادة ++
قبل المتغير. وأخيرًا، يُزاد i = 4
إلى 5
، ولا يتحقق شرط حلقة التكرار while(5 < 5)
في هذه الحالة، وتتوقف الحلقة. لذلك لا يظهر العدد 5
.
- من 1 إلى 5
let i = 0; while (i++ < 5) alert( i );
القيمة الأولى هي i = 1
. يزيد النموذج اللاحق ++i
المتغير i
ثم يُرجع القيمة القديمة، لذلك ستستخدم الموازنة i++ < 5
التعبير i = 0
(على عكس ++i < 5
). لكن استدعاء الدالة alert
منفصل وتُنفَّذ بعد معامل الزيادة والموازنة، لذلك تحصل على القيمة الحالية للمتغير i = 1
. ثم تتبعها الأعداد 2، 3، 4. عند i = 4
، يزيد النموذج السابق i++
قيمة المتغير ويستخدم العدد 5
في الموازنة ولكن هنا لدينا النموذج اللاحق ++i
. أي أن قيمة المتغير i
تصبح 5
، لكنه يُرجع القيمة القديمة. وبهذا تصبح الموازنة while(4 < 5)
- صحيحة، وتُنفذ الدالة alert
. القيمة i = 5
آنذاك هي القيمة الأخيرة، لأن الشرط في التكرار التالي while(5 < 5)
غير مُحقق.
ما القيم التي ستظهرها حلقة التكرار for
؟
الأهمية: 4
سجّل القيمة الناتجة من كل حلقة تكرار، ثم قارنها بالإجابة. هل تُظهر الدالة alert
نفس القيم في الحلقتين أم لا؟
-
النموذج اللاحق
++i
:
for (let i = 0; i < 5; i++) alert( i );
-
النموذج السابق
i++
:
for (let i = 0; i < 5; ++i) alert( i );
الحل
الإجابة في كلتا الحلقتين: من 0
إلى 4
.
for (let i = 0; i < 5; ++i) alert( i ); for (let i = 0; i < 5; i++) alert( i );
يمكنك بسهولة استنتاج التالي من الخوارزمية for
:
-
تُنفذ التعليمة
i = 0
مرة واحدة في البداية. -
يتم يُتحقَّق من الشرط
i < 5
. -
إذا كان الشرط محققًا
true
- يُنفذ جسم الحلقةalert(i)
ويليه معامل الزيادة++i
.
معامل الزيادة ++i
منفصل عن الشرط في الحالتين، فهو عبارة عن تعليمة أخرى. لا تُستخدَم القيمة التي يعيدها معامل الزيادة هنا، لذلك لا يوجد فرق بين النموذجين ++i
و i++
.
إخراج الأعداد الزوجية باستخدام حلقة التكرار
الأهمية: 5
استخدم حلقة التكرار for
لإظهار الأعداد الزوجية من 2
إلى 10
.
الحل
for (let i = 2; i <= 10; i++) { if (i % 2 == 0) { alert( i ); } }
يمكنك استخدام معامل باقي القسمة (modulo) %
للحصول على باقي القسمة والتحقق من التكافؤ هنا.
استبدال حلقة التكرار for
بحلقة التكرار while
الأهمية: 5
أعد كتابة الشيفرة باستبدال حلقة التكرار for
بحلقة التكرار while
دون تغيير سلوك الشيفرة (يجب أن يظل ناتج حلقة التكرار كما هو).
for (let i = 0; i < 3; i++) { alert( `number ${i}!` ); }
الحل
let i = 0; while (i < 3) { alert( `number ${i}!` ); i++; }
كرر حتى يكون الإدخال صحيحًا
الأهمية: 5
اكتب حلقة تكرار تطلب من المستخدم إدخال عدد أكبر من 100. إذا أدخل المستخدم عدد آخر، فاطلب منه الإدخال مرة أخرى.
يجب أن تسأل حلقة التكرار عن العدد حتى يُدخل المستخدم عدد أكبر من 100 أو يلغي الإدخال / يُدخل سطرًا فارغًا. في هذا التمرين، يدخل المستخدم الأعداد فقط أي ليس هناك حاجة لتنفيذ معالجة خاصة للتحقق من القيم المدخلة.
الحل
let num; do { num = prompt("Enter a number greater than 100?", 0); } while (num <= 100 && num);
تتكرر حلقة التكرار do..while
طالما أن الشرط num <= 100 && num
محقق:
-
الجزء الأول من الشرط
num <= 100
- أي أن القيمة التي أدخلها المستخدم لا تزال أقل من100
. -
الجزء الثاني من الشرط
&& num
- لا يتحقق هذا الجزء إذا كان الإدخالnull
أو سلسلة نصية فارغة. تتوقف حلقة التكرارwhile
في هذه الحالة أيضًا.
ملاحظة: إذا كان المتغير num
فارغًا null
، فسيكون الجزء الأول من الشرط num <= 100
صحيحًا true
، دون الجزء الثاني من الشرط لن تتوقف الحلقة إذا نقر المستخدم على زر الإلغاء (CANCEL). لذا لا يمكن الاستغناء عن أي الجزئين.
إظهار الأعداد الأولية
الأهمية: 3
يسمى العدد عددًا أولي (prime) إذا كان عددًا صحيحًا أكبر من 1
، ولا يقبل القسمة (القسمة دون باقي قسمة) إلا على نفسه وعلى العدد 1
. بعبارة أخرى، n > 1
المتغير n
عبارة عن عدد أولي إذا كان لا يقبل القسمة بالتساوي على أي عدد باستثناء 1
و n
. على سبيل المثال، العدد 5
هو عدد أولي، لأنه لا يمكن تقسيمه بالتساوي دون وجود باقي قسمة بمقدار 2
، و 3
و4
.
اكتب الشيفرة التي تخرج الأعداد الأولية في المجال من 2
إلى n
. إذا كانت n = 10
مثلًا، فستكون النتيجة 2,3,5,7
.
ملاحظة: يجب أن تعمل الشيفرة من أجل أي قيمة للمتغير n
، أي لا يكون المتغير n
معرّف لقيمة ثابتة.
الحل
هناك العديد من الخوارزميات لهذا التمرين:
- كتابة الشيفرة باستخدام حلقة تكرار متداخلة:
For each i in the interval { check if i has a divisor from 1..i if yes => the value is not a prime if no => the value is a prime, show it }
- كتابة الشيفرة باستخدام اللافتة (label):
let n = 10; nextPrime: for (let i = 2; i <= n; i++) { // i لكل قيمة من قيم for (let j = 2; j < i; j++) { // ابحث عن المقسوم عليه if (i % j == 0) continue nextPrime; // ليس عددًا أوليًا، انتقل للتكرار التالي } alert( i ); // عددٌ أولي }
هناك مساحة كبيرة لتحسين الشيفرة، على سبيل المثال يمكنك البحث عن القواسم من 2
إلى الجذر التربيعي ل i
(مثال: القواسم الموجبة للعدد 24 هي 1، 2، 3، 4، 6، 8، 12، 24). على أي حال، إذا كنت تريد تنفيذ التمرين السابق على مجالات كبيرة، فستحتاج إلى تغيير النهج والاعتماد على الرياضيات المتقدمة والخوارزميات المعقدة مثل المنخل التربيعي (Quadratic sieve) أو منخل الأعداد العام (General number field sieve) وما إلى ذلك.
ترجمة -وبتصرف- للفصل Loops: while and for من كتاب The JavaScript Language
اقرأ أيضًا
- المقال التالي: التعليمة switch
- المقال السابق: المعاملات المنطقية
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.