الفصل 02
بنية البرنامج
ويتوهج قلبي أحمرَ زاهياً تحت جلدي الغشائي الشفاف، وعليهم أن يحقنوني بـ10 سم مكعب من JavaScript لأعود. (أستجيب جيداً للسموم في الدم.) يا رجل، تلك المادة ستقذف حبات الخوخ من خياشيمك مباشرة!
— _why, Why's (Poignant) Guide to Ruby

في هذا الفصل، سنبدأ بفعل أشياء يمكن أن تُسمى فعلاً برمجة. سنوسع تمكننا من لغة JavaScript بما يتجاوز الأسماء والجمل الناقصة التي رأيناها حتى الآن، إلى حد نستطيع فيه التعبير بنثر ذي معنى.
التعبيرات والجمل
في الفصل 1، أنشأنا قيماً وطبقنا عليها معاملات للحصول على قيم جديدة. إن إنشاء قيم كهذه هو الجوهر الأساسي لأي برنامج JavaScript. لكن هذا الجوهر يجب أن يُؤطَّر في بنية أكبر ليكون مفيداً. وهذا ما سنتناوله في هذا الفصل.
يُسمى جزء الشيفرة الذي ينتج قيمة تعبيراً (expression). وكل قيمة تُكتب حرفياً (مثل 22 أو "psychoanalysis") هي تعبير. والتعبير الموضوع بين قوسين هو أيضاً تعبير، وكذلك المعامل الثنائي المطبَّق على تعبيرين أو المعامل الأحادي المطبَّق على تعبير واحد.
يوضح هذا جزءاً من جمال الواجهة القائمة على اللغة. يمكن للتعبيرات أن تحتوي تعبيرات أخرى بطريقة تشبه تداخل الجمل الفرعية في اللغات البشرية — إذ يمكن للجملة الفرعية أن تحتوي جملها الفرعية الخاصة، وهكذا. وهذا يتيح لنا بناء تعبيرات تصف حسابات معقدة كما نشاء.
إذا كان التعبير يقابل جزء جملة، فإن الجملة (statement) في JavaScript تقابل جملة كاملة. والبرنامج قائمة من الجمل.
أبسط أنواع الجمل هو تعبير تليه فاصلة منقوطة. وهذا برنامج:
1;
!false;
لكنه برنامج عديم الفائدة. يمكن للتعبير أن يكتفي بإنتاج قيمة، يمكن للشيفرة المحيطة به استخدامها. غير أن الجملة قائمة بذاتها، لذا إن لم تؤثر في العالم فهي عديمة الفائدة. قد تعرض شيئاً على الشاشة، كما في console.log، أو تغيّر حالة الآلة بطريقة تؤثر في الجمل التي تأتي بعدها. تُسمى هذه التغييرات تأثيرات جانبية (side effects). والجمل في المثال السابق تنتج فقط القيمتين 1 وtrue ثم تطرحهما فوراً. لا يترك هذا أي أثر في العالم إطلاقاً. وعند تشغيل هذا البرنامج، لا يحدث شيء ملحوظ.
في بعض الحالات، تسمح لك JavaScript بحذف الفاصلة المنقوطة في نهاية الجملة. وفي حالات أخرى، يجب أن تكون موجودة، وإلا سيُعامل السطر التالي كجزء من الجملة نفسها. وقواعد تحديد متى يمكن حذفها بأمان معقدة نوعاً ما وعرضة للخطأ. لذا في هذا الكتاب، ستحصل كل جملة تحتاج فاصلة منقوطة عليها دائماً. وأنصحك بفعل الشيء نفسه، على الأقل حتى تتعلم أكثر عن دقائق حذف الفواصل المنقوطة.
الارتباطات
كيف يحافظ البرنامج على حالة داخلية؟ وكيف يتذكر الأشياء؟ رأينا كيفية إنتاج قيم جديدة من قيم قديمة، لكن هذا لا يغيّر القيم القديمة، ويجب استخدام القيمة الجديدة فوراً وإلا تبددت مجدداً. للإمساك بالقيم والاحتفاظ بها، توفر JavaScript شيئاً يسمى ارتباطاً (binding)، أو متغيراً.
let caught = 5 * 5;
يمنحنا هذا نوعاً ثانياً من الجمل. والكلمة الخاصة (الكلمة المفتاحية) let تدل على أن هذه الجملة ستعرف ارتباطاً. ويتبعها اسم الارتباط، وإن أردنا إعطاءه قيمة فوراً، يتبعها معامل = وتعبير.
ينشئ المثال ارتباطاً يسمى caught ويستخدمه للإمساك بالعدد الناتج عن ضرب 5 في 5.
بعد تعريف ارتباط، يمكن استخدام اسمه كتعبير. وقيمة هذا التعبير هي القيمة التي يحملها الارتباط حالياً. إليك مثالاً:
let ten = 10;
console.log(ten * ten);
// → 100
عندما يشير ارتباط إلى قيمة، فهذا لا يعني أنه مرتبط بها إلى الأبد. يمكن استخدام المعامل = في أي وقت مع الارتباطات الموجودة لفصلها عن قيمتها الحالية وجعلها تشير إلى قيمة جديدة:
let mood = "light";
console.log(mood);
// → light
mood = "dark";
console.log(mood);
// → dark
تخيل الارتباطات مجسّات (tentacles) لا صناديق. فهي لا تحتوي القيم؛ بل تمسك بها — ويمكن لارتباطين الإشارة إلى القيمة نفسها. ولا يمكن للبرنامج الوصول إلا إلى القيم التي ما زال يملك مرجعاً إليها. وعندما تحتاج إلى تذكر شيء ما، إما أن تنبت مجسّاً جديداً للإمساك به، أو تعيد توجيه أحد مجسّاتك الموجودة إليه.
لننظر إلى مثال آخر. لتتذكر عدد الدولارات التي ما زال لويجي مديناً بها لك، تنشئ ارتباطاً. وعندما يسدد 35 دولاراً، تعطي هذا الارتباط قيمة جديدة.
let luigisDebt = 140;
luigisDebt = luigisDebt - 35;
console.log(luigisDebt);
// → 105
عندما تعرّف ارتباطاً دون إعطائه قيمة، لا يجد المجسّ شيئاً يمسكه، فينتهي في الهواء. وإذا طلبت قيمة ارتباط فارغ، فستحصل على القيمة undefined.
ويمكن لجملة let واحدة تعريف عدة ارتباطات. ويجب فصل التعريفات بفواصل:
let one = 1, two = 2;
console.log(one + two);
// → 3
ويمكن أيضاً استخدام الكلمتين var وconst لإنشاء ارتباطات، بطريقة مشابهة لـ let.
var name = "Ayda";
const greeting = "Hello ";
console.log(greeting + name);
// → Hello Ayda
الأولى منهما، var (اختصار «variable»)، هي الطريقة التي كانت تُعلن بها الارتباطات في JavaScript قبل 2015، حين لم يكن let موجوداً بعد. سأعود إلى الطريقة الدقيقة التي تختلف بها عن let في الفصل التالي. في الوقت الحالي، تذكر أنها تفعل الشيء نفسه غالباً، لكننا سنادراً ما نستخدمها في هذا الكتاب لأنها تتصرف بغرابة في بعض الحالات.
أما الكلمة const فتعني ثابتاً (constant). وهي تعرّف ارتباطاً ثابتاً يشير إلى القيمة نفسها طوال حياته. وهذا مفيد للارتباطات التي تمنح قيمة اسماً لتسهيل الإشارة إليها لاحقاً.
أسماء الارتباطات
يمكن أن تكون أسماء الارتباطات أي سلسلة من حرف واحد أو أكثر. ويمكن أن تكون الأرقام جزءاً من أسماء الارتباطات — catch22 اسم صالح مثلاً — لكن يجب ألا يبدأ الاسم برقم. وقد يتضمن اسم الارتباط علامات الدولار ($) أو الشرطات السفلية (_) لكن دون أي علامات ترقيم أو رموز خاصة أخرى.
الكلمات ذات المعنى الخاص، مثل let، هي كلمات مفتاحية، ولا يجوز استخدامها كأسماء ارتباطات. وهناك أيضاً عدد من الكلمات «المحجوزة للاستخدام» في إصدارات JavaScript المستقبلية، والتي لا يمكن استخدامها كذلك كأسماء ارتباطات. والقائمة الكاملة للكلمات المفتاحية والكلمات المحجوزة طويلة نوعاً ما:
break case catch class const continue debugger default
delete do else enum export extends false finally for
function if implements import interface in instanceof let
new package private protected public return static super
switch this throw true try typeof var void while with yield
لا تقلق بشأن حفظ هذه القائمة. وعندما ينتج عن إنشاء ارتباط خطأ نحوي غير متوقع، تحقق مما إذا كنت تحاول تعريف كلمة محجوزة.
البيئة
تسمى مجموعة الارتباطات وقيمها الموجودة في لحظة معينة البيئة (environment). وعند بدء تشغيل برنامج، لا تكون هذه البيئة فارغة. فهي تحتوي دائماً على ارتباطات تشكل جزءاً من معيار اللغة، وفي معظم الأحيان تحتوي أيضاً على ارتباطات توفر طرقاً للتفاعل مع النظام المحيط. فمثلاً، في المتصفح توجد دوال للتفاعل مع الموقع المحمَّل حالياً وقراءة إدخال الفأرة ولوحة المفاتيح.
الدوال
الكثير من القيم الموفرة في البيئة الافتراضية من النوع دالة. والدالة قطعة برنامج ملفوفة في قيمة. ويمكن تطبيق مثل هذه القيم لتشغيل البرنامج الملفوف. فمثلاً، في بيئة المتصفح، يحمل الارتباط prompt دالة تعرض مربع حوار صغيراً يطلب إدخال المستخدم. وتُستخدم هكذا:
prompt("Enter passcode");

يُسمى تنفيذ الدالة استدعاءها أو نداءها أو تطبيقها. ويمكنك استدعاء دالة بوضع قوسين بعد تعبير ينتج قيمة دالة. وعادةً ستستخدم مباشرة اسم الارتباط الذي يحمل الدالة. والقيم الموضوعة بين القوسين تُعطى للبرنامج داخل الدالة. وفي المثال، تستخدم دالة prompt النص الذي نعطيها إياه كنص يُعرض في مربع الحوار. وتسمى القيم المعطاة للدوال معطيات (arguments). وقد تحتاج دوال مختلفة إلى عدد مختلف أو أنواع مختلفة من المعطيات.
لا تُستخدم دالة prompt كثيراً في برمجة الويب الحديثة، لأنك لا تملك تحكماً في شكل الحوار الناتج غالباً، لكنها قد تكون مفيدة في البرامج التجريبية والتجارب.
دالة console.log
في الأمثلة، استخدمت console.log لإخراج القيم. وتوفر معظم أنظمة JavaScript (بما فيها جميع متصفحات الويب الحديثة وNode.js) دالة console.log تكتب معطياتها إلى بعض أجهزة إخراج النصوص. وفي المتصفحات، يظهر الإخراج في وحدة تحكم JavaScript. وهذا الجزء من واجهة المتصفح مخفي افتراضياً، لكن معظم المتصفحات تفتحه عند الضغط على F12 أو، على Mac، command-option-I. وإن لم ينجح ذلك، فابحث في القوائم عن عنصر باسم Developer Tools أو ما شابه.
عند تشغيل الأمثلة (أو شيفرتك الخاصة) في صفحات هذا الكتاب، سيظهر إخراج console.log بعد المثال بدلاً من وحدة تحكم JavaScript في المتصفح.
let x = 30;
console.log("the value of x is", x);
// → the value of x is 30
رغم أن أسماء الارتباطات لا يمكن أن تحتوي على نقطة، فإن console.log يحتوي عليها. والسبب أن console.log ليس ارتباطاً بسيطاً، بل تعبير يسترجع الخاصية log من القيمة التي يحملها الارتباط console. وسنكتشف بالضبط ما يعنيه هذا في الفصل 4.
القيم المعادة
عرض مربع حوار أو كتابة نص على الشاشة هو تأثير جانبي. وكثير من الدوال مفيدة بسبب تأثيراتها الجانبية. وقد تنتج الدوال قيماً أيضاً، وفي هذه الحالة لا تحتاج إلى تأثير جانبي لتكون مفيدة. فمثلاً، تأخذ الدالة Math.max أي عدد من المعطيات العددية وتعيد أكبرها.
console.log(Math.max(2, 4));
// → 4
عندما تنتج دالة قيمة، يقال إنها تعيد (return) تلك القيمة. وكل ما ينتج قيمة هو تعبير في JavaScript، ما يعني أن استدعاءات الدوال يمكن استخدامها داخل تعبيرات أكبر. وفي الشيفرة التالية، يُستخدم استدعاء Math.min، وهو عكس Math.max، كجزء من تعبير جمع:
console.log(Math.min(2, 4) + 100);
// → 102
سيشرح الفصل 3 كيفية كتابة دوالك الخاصة.
مسار التحكم
عندما يحتوي برنامجك على أكثر من جملة، تُنفذ الجمل كما لو كانت قصة، من الأعلى إلى الأسفل. فمثلاً، البرنامج التالي فيه جملتان. الأولى تطلب رقماً من المستخدم، والثانية، التي تُنفذ بعد الأولى، تعرض مربع ذلك الرقم:
let theNumber = Number(prompt("Pick a number"));
console.log("Your number is the square root of " +
theNumber * theNumber);
تحوّل الدالة Number قيمة إلى عدد. ونحتاج هذا التحويل لأن نتيجة prompt قيمة نصية، ونريد عدداً. وهناك دوال مشابهة تسمى String وBoolean تحوّل القيم إلى هذين النوعين.
إليك التمثيل التخطيطي البسيط نوعاً ما لمسار التحكم المستقيم:
التنفيذ الشرطي
ليست كل البرامج طرقاً مستقيمة. قد نريد مثلاً إنشاء طريق متشعب يأخذ فيه البرنامج الفرع المناسب حسب الحالة القائمة. ويسمى هذا التنفيذ الشرطي.
يُنشأ التنفيذ الشرطي بالكلمة المفتاحية if في JavaScript. وفي الحالة البسيطة، نريد تنفيذ بعض الشيفرة إذا، وفقط إذا، تحقق شرط معين. وقد نريد مثلاً عرض مربع المُدخل فقط إذا كان المُدخل عدداً فعلاً:
let theNumber = Number(prompt("Pick a number"));
if (!Number.isNaN(theNumber)) {
console.log("Your number is the square root of " +
theNumber * theNumber);
}
مع هذا التعديل، إذا أدخلت "parrot" فلن يظهر أي إخراج.
تنفذ الكلمة المفتاحية if جملة أو تتخطاها حسب قيمة تعبير منطقي. وتُكتب الجملة الحاسمة بعد الكلمة المفتاحية، بين قوسين، وتليها الجملة المراد تنفيذها.
ودالة Number.isNaN دالة JavaScript قياسية تعيد true فقط إذا كان المعطى لها هو NaN. وتعيد دالة Number بالمناسبة NaN عند إعطائها نصاً لا يمثل عدداً صالحاً. وهكذا تصبح الجملة الشرطية: «ما لم يكن theNumber ليس عدداً، فنفّذ هذا».
الجملة التي تلي if ملفوفة بأقواس ({ و}) في هذا المثال. ويمكن استخدام الأقواس لتجميع أي عدد من الجمل في جملة واحدة تسمى كتلة (block). وكان يمكنك أيضاً حذفها في هذه الحالة، لأنها تحمل جملة واحدة فقط، لكن لتجنب التفكير فيما إذا كانت ضرورية، يستخدم معظم مبرمجي JavaScript الأقواس في كل جملة ملفوفة كهذه. وسنتبع هذه العادة غالباً في هذا الكتاب، باستثناء الأسطر الواحدة العرضية.
if (1 + 1 == 2) console.log("It's true");
// → It's true
غالباً لن تكون لديك شيفرة تُنفذ عند تحقق الشرط فقط، بل أيضاً شيفرة تتعامل مع الحالة الأخرى. ويُمثل هذا المسار البديل بالسهم الثاني في المخطط. ويمكنك استخدام الكلمة المفتاحية else مع if لإنشاء مساري تنفيذ بديلين منفصلين:
let theNumber = Number(prompt("Pick a number"));
if (!Number.isNaN(theNumber)) {
console.log("Your number is the square root of " +
theNumber * theNumber);
} else {
console.log("Hey. Why didn't you give me a number?");
}
إذا كان لديك أكثر من مسارين للاختيار بينهما، يمكنك «سلسلة» أزواج متعددة من if/else. إليك مثالاً:
let num = Number(prompt("Pick a number"));
if (num < 10) {
console.log("Small");
} else if (num < 100) {
console.log("Medium");
} else {
console.log("Large");
}
سيتحقق البرنامج أولاً مما إذا كان num أقل من 10. فإن كان كذلك، يختار ذلك الفرع ويعرض "Small" وينتهي. وإن لم يكن، يسلك فرع else الذي يحتوي بدوره على if ثانٍ. وإذا تحقق الشرط الثاني (< 100)، فهذا يعني أن العدد 10 على الأقل وأقل من 100، ويُعرض "Medium". وإن لم يتحقق، يُختار الفرع الثاني والأخير else.
ويكون مخطط هذا البرنامج على هذا النحو:
حلقتا while وdo
فكر في برنامج يخرج كل الأعداد الزوجية من 0 إلى 12. إحدى طرق كتابته كما يلي:
console.log(0);
console.log(2);
console.log(4);
console.log(6);
console.log(8);
console.log(10);
console.log(12);
هذا يعمل، لكن فكرة كتابة برنامج هي جعل العمل أقل، لا أكثر. ولو احتجنا كل الأعداد الزوجية الأقل من 1000، فلن يكون هذا الأسلوب عملياً. ما نحتاجه هو طريقة لتشغيل قطعة شيفرة عدة مرات. ويسمى هذا الشكل من مسار التحكم حلقة (loop).
يتيح لنا مسار التحكم الحلقي العودة إلى نقطة سابقة في البرنامج وتكرارها بحالة برنامجنا الحالية. وإذا دمجنا هذا مع ارتباط يعدّ، أمكننا فعل شيء كهذا:
let number = 0;
while (number <= 12) {
console.log(number);
number = number + 2;
}
// → 0
// → 2
// … وهكذا
تنشئ الجملة التي تبدأ بالكلمة المفتاحية while حلقة. وتتبع كلمة while تعبير بين قوسين ثم جملة، تماماً مثل if. وتظل الحلقة تدخل تلك الجملة ما دام التعبير ينتج قيمة تعطي true عند تحويلها إلى قيمة منطقية.
ويوضح ارتباط number الطريقة التي يمكن بها للارتباط تتبع تقدم البرنامج. ففي كل مرة تتكرر الحلقة، يحصل number على قيمة تزيد بمقدار 2 عن قيمته السابقة. وفي بداية كل تكرار، يُقارن بالعدد 12 لتحديد ما إذا كان عمل البرنامج قد انتهى.
وكمثال يؤدي شيئاً مفيداً فعلاً، يمكننا الآن كتابة برنامج يحسب ويعرض قيمة 2¹⁰ (2 مرفوعاً للقوة 10). نستخدم ارتباطين: واحداً لتتبع النتيجة وآخر لعدّ عدد مرات ضرب هذه النتيجة في 2. وتختبر الحلقة ما إذا كان الارتباط الثاني قد بلغ 10 بعد، وإن لم يبلغ، تحدّث كلا الارتباطين.
let result = 1;
let counter = 0;
while (counter < 10) {
result = result * 2;
counter = counter + 1;
}
console.log(result);
// → 1024
كان بإمكان العداد أن يبدأ أيضاً من 1 ويتحقق من <= 10، لكن لأسباب ستتضح في الفصل 4، من الجيد الاعتياد على العد من 0.
لاحظ أن JavaScript تحتوي أيضاً على معامل للرفع إلى قوة (2 ** 10)، وكنت ستستخدمه لحساب هذا في شيفرة حقيقية — لكن ذلك كان سيفسد المثال.
وحلقة do بنية تحكم مشابهة لحلقة while. وهي تختلف في نقطة واحدة فقط: حلقة do تنفذ جسمها دائماً مرة واحدة على الأقل، ولا تبدأ في اختبار ما إذا كان ينبغي أن تتوقف إلا بعد ذلك التنفيذ الأول. وللتعبير عن ذلك، يظهر الاختبار بعد جسم الحلقة:
let yourName;
do {
yourName = prompt("Who are you?");
} while (!yourName);
console.log("Hello " + yourName);
سيجبرك هذا البرنامج على إدخال اسم. وسيسأل مرة تلو الأخرى حتى يحصل على شيء ليس نصاً فارغاً. وإن تطبيق المعامل ! سيحول القيمة إلى النوع المنطقي قبل نفيها، وكل النصوص باستثناء "" تتحول إلى true. وهذا يعني أن الحلقة تستمر في الدوران حتى تقدم اسماً غير فارغ.
إزاحة الشيفرة
في الأمثلة، كنت أضيف مسافات أمام الجمل التي تشكل جزءاً من جملة أكبر. وهذه المسافات غير مطلوبة — فسيقبل الحاسوب البرنامج دونها بلا مشكلة. بل إن فواصل الأسطر نفسها في البرامج اختيارية. ويمكنك كتابة برنامج في سطر واحد طويل إن أردت.
ودور هذه الإزاحة داخل الكتل هو إبراز بنية الشيفرة للقارئ البشري. ففي الشيفرة التي تُفتح فيها كتل جديدة داخل كتل أخرى، قد يصعب رؤية أين تنتهي إحدى الكتل وتبدأ أخرى. ومع الإزاحة الصحيحة، يقابل الشكل البصري للبرنامج شكل الكتل بداخله. وأحب استخدام مسافتين لكل كتلة مفتوحة، لكن الأذواق تختلف — فبعض الناس يستخدمون أربع مسافات، وبعضهم يستخدم محارف الجدولة. والمهم أن تضيف كل كتلة جديدة القدر نفسه من المسافة.
if (false != true) {
console.log("That makes sense.");
if (1 < 2) {
console.log("No surprise there.");
}
}
ومعظم برامج تحرير الشيفرة (بما فيها الموجود في هذا الكتاب) ستساعدك بإزاحة الأسطر الجديدة تلقائياً بالقدر المناسب.
حلقات for
تتبع حلقات كثيرة النمط المعروض في أمثلة while. فأولاً يُنشأ ارتباط «عداد» لتتبع تقدم الحلقة. ثم تأتي حلقة while، عادةً بتعبير اختبار يتحقق مما إذا كان العداد قد بلغ قيمة نهايته. وفي نهاية جسم الحلقة، يُحدَّث العداد لتتبع التقدم.
ولأن هذا النمط شائع جداً، توفر JavaScript واللغات المشابهة صيغة أقصر قليلاً وأشمل، هي حلقة for:
for (let number = 0; number <= 12; number = number + 2) {
console.log(number);
}
// → 0
// → 2
// … وهكذا
هذا البرنامج مكافئ تماماً لمثال طباعة الأعداد الزوجية السابق. والتغيير الوحيد أن كل الجمل المتعلقة بـ«حالة» الحلقة مجمعة معاً بعد for.
ويجب أن يحتوي القوسان بعد الكلمة المفتاحية for على فاصلتين منقوطتين. فالجزء قبل الفاصلة الأولى يُهيئ الحلقة، عادةً بتعريف ارتباط. والجزء الثاني هو التعبير الذي يتحقق مما إذا كان يجب أن تستمر الحلقة. والجزء الأخير يحدّث حالة الحلقة بعد كل تكرار. وفي معظم الحالات، يكون هذا أقصر وأوضح من بنية while.
وهذه هي الشيفرة التي تحسب 2¹⁰ باستخدام for بدلاً من while:
let result = 1;
for (let counter = 0; counter < 10; counter = counter + 1) {
result = result * 2;
}
console.log(result);
// → 1024
الخروج من حلقة
ليس إنتاج شرط الحلقة false هو الطريقة الوحيدة لانتهاء الحلقة. فلجملة break أثر القفز فوراً خارج الحلقة المحيطة. ويُعرض استخدامها في البرنامج التالي الذي يجد أول عدد أكبر من أو يساوي 20 ويقبل القسمة على 7:
for (let current = 20; ; current = current + 1) {
if (current % 7 == 0) {
console.log(current);
break;
}
}
// → 21
واستخدام معامل الباقي (%) طريقة سهلة لاختبار ما إذا كان عدد يقبل القسمة على عدد آخر. فإن كان كذلك، يكون باقي قسمتهما صفراً.
ولا تحتوي بنية for في المثال على جزء يتحقق من نهاية الحلقة. وهذا يعني أن الحلقة لن تتوقف أبداً ما لم تُنفذ جملة break بداخلها.
ولو حذفت جملة break تلك أو كتبت عن غير قصد شرط نهاية ينتج true دائماً، لعلّ برنامجك في حلقة لا نهائية. والبرنامج العالق في حلقة لا نهائية لن ينتهي تشغيله أبداً، وهذا أمر سيئ عادة.
وإذا أنشأت حلقة لا نهائية في أحد الأمثلة في هذه الصفحات، فسيُسألك عادةً بعد بضع ثوانٍ ما إذا كنت تريد إيقاف النص البرمجي. وإن فشل ذلك، فسيتعين عليك إغلاق التبويب الذي تعمل فيه للتعافي.
وتشبه الكلمة المفتاحية continue كلمة break من حيث تأثيرها في تقدم الحلقة. فعندما يُصادف continue في جسم حلقة، يقفز التحكم خارج الجسم ويواصل التكرار التالي للحلقة.
تحديث الارتباطات بإيجاز
خصوصاً عند التكرار، يحتاج البرنامج غالباً إلى «تحديث» ارتباط ليحمل قيمة مبنية على قيمته السابقة.
counter = counter + 1;
وتوفر JavaScript اختصاراً لهذا:
counter += 1;
وتعمل اختصارات مشابهة مع معاملات أخرى كثيرة، مثل result *= 2 لمضاعفة result أو counter -= 1 للعد تنازلياً.
ويتيح لنا هذا تقصير مثال العد أكثر:
for (let number = 0; number <= 12; number += 2) {
console.log(number);
}
وبالنسبة إلى counter += 1 وcounter -= 1، هناك مكافئان أقصر: counter++ وcounter--.
التوزيع حسب قيمة باستخدام switch
ليس غريباً أن تبدو الشيفرة هكذا:
if (x == "value1") action1();
else if (x == "value2") action2();
else if (x == "value3") action3();
else defaultAction();
وتوجد بنية تسمى switch يقصد بها التعبير عن مثل هذا «التوزيع» بطريقة أكثر مباشرة. وللأسف، الصيغة التي تستخدمها JavaScript لهذا (والتي ورثتها عن سلسلة لغات البرمجة C/Java) ركيكة نوعاً ما — وقد تبدو سلسلة جمل if أفضل. إليك مثالاً:
switch (prompt("What is the weather like?")) {
case "rainy":
console.log("Remember to bring an umbrella.");
break;
case "sunny":
console.log("Dress lightly.");
case "cloudy":
console.log("Go outside.");
break;
default:
console.log("Unknown weather type!");
break;
}
ويمكنك وضع أي عدد من تسميات case داخل الكتلة التي يفتحها switch. وسيبدأ البرنامج التنفيذ عند التسمية المقابلة للقيمة التي أُعطيها switch، أو عند default إن لم توجد قيمة مطابقة. وسيواصل التنفيذ، حتى عبر تسميات أخرى، حتى يصل إلى جملة break. وفي بعض الحالات، مثل حالة "sunny" في المثال، يمكن استخدام هذا لمشاركة بعض الشيفرة بين الحالات (فهو يوصي بالخروج إلى الخارج في الطقس المشمس والغائم معاً). لكن كن حذراً — فمن السهل نسيان جملة break كهذه، ما يجعل البرنامج ينفذ شيفرة لا تريد تنفيذها.
كتابة الأحرف الكبيرة
لا يمكن أن تحتوي أسماء الارتباطات على مسافات، ومع ذلك يكون من المفيد غالباً استخدام عدة كلمات لوصف ما يمثله الارتباط بوضوح. وهذه تقريباً خياراتك لكتابة اسم ارتباط من عدة كلمات:
fuzzylittleturtle
fuzzy_little_turtle
FuzzyLittleTurtle
fuzzyLittleTurtle
قد يكون النمط الأول صعب القراءة. وأنا أحب شكل الشرطات السفلية، رغم أن ذلك النمط مؤلم قليلاً في الكتابة. وتتبع دوال JavaScript القياسية، ومعظم مبرمجي JavaScript، النمط الأخير — إذ يبدؤون كل كلمة بحرف كبير باستثناء الأولى. وليس صعباً الاعتياد على تفاصيل صغيرة كهذه، والشيفرة ذات أنماط التسمية المختلطة قد تكون منفرة في القراءة، لذا نتبع هذه العادة.
وفي حالات قليلة، مثل دالة Number، يُكتب الحرف الأول من الارتباط بحرف كبير أيضاً. وقد فُعل ذلك لتمييز هذه الدالة كدالة بناء (constructor). وسيتضح ما هي دالة البناء في الفصل 6. والمهم الآن ألا تنزعج من هذا الافتقار الظاهري إلى الاتساق.
التعليقات
غالباً ما لا تنقل الشيفرة الخام كل المعلومات التي تريد أن ينقلها البرنامج إلى القراء البشر، أو تنقلها بطريقة غامضة قد لا يفهمها الناس. وفي أحيان أخرى، قد تريد فقط تضمين بعض الأفكار ذات الصلة كجزء من برنامجك. ولهذا الغرض توجد التعليقات (comments).
والتعليق قطعة نص تشكل جزءاً من برنامج لكن الحاسوب يتجاهلها تماماً. ولJavaScript طريقتان لكتابة التعليقات. ولكتابة تعليق من سطر واحد، يمكنك استخدام محرفي الشرطة المائلة (//) ثم نص التعليق بعدهما:
let accountBalance = calculateBalance(account);
// إنه وادٍ أخضر يغني فيه نهر
accountBalance.adjust();
// يلتقط بجنون خِرَقاً بيضاء في العشب.
let report = new Report();
// حيث ترنّ الشمس على الجبل المتشامخ:
addToReport(accountBalance, report);
// إنه وادٍ صغير، يزبد كالضوء في كأس.
وتعليق // يمتد إلى نهاية السطر فقط. أما مقطع النص بين /* و*/ فيُتجاهل بالكامل، بصرف النظر عما إذا كان يحتوي على فواصل أسطر. وهذا مفيد لإضافة كتل من المعلومات عن ملف أو جزء من برنامج:
/*
وجدت هذا العدد أول مرة مخربشاً على ظهر دفتر
قديم. ومنذ ذلك الحين، كثيراً ما مرّ بي، ظاهراً في
أرقام هواتف وأرقام تسلسلية لمنتجات اشتريتها.
من الواضح أنه يحبني، لذا قررت الاحتفاظ به.
*/
const myNumber = 11213;
الخلاصة
تعرف الآن أن البرنامج يُبنى من جمل، وهذه الجمل تحتوي أحياناً على مزيد من الجمل. وتميل الجمل إلى احتواء تعبيرات، وهذه بدورها يمكن بناؤها من تعبيرات أصغر.
ووضع الجمل إحداها بعد الأخرى يمنحك برنامجاً يُنفذ من الأعلى إلى الأسفل. ويمكنك إدخال اضطرابات في مسار التحكم باستخدام جمل شرطية (if وelse وswitch) وحلقات (while وdo وfor).
ويمكن استخدام الارتباطات لحفظ قطع من البيانات تحت اسم، وهي مفيدة لتتبع الحالة في برنامجك. والبيئة هي مجموعة الارتباطات المعرَّفة. وتضع أنظمة JavaScript دائماً عدداً من الارتباطات القياسية المفيدة في بيئتك.
والدوال قيم خاصة تغلف قطعة برنامج. ويمكنك استدعاؤها بكتابة functionName(argument1, argument2). واستدعاء الدالة هذا تعبير وقد ينتج قيمة.
التمارين
إذا لم تكن متأكداً من كيفية اختبار حلولك للتمارين، فراجع المقدمة.
يبدأ كل تمرين بوصف للمسألة. اقرأ هذا الوصف وحاول حل التمرين. وإذا واجهتك مشكلات، فكر في قراءة التلميحات بعد التمرين. ويمكنك العثور على حلول كاملة للتمارين على الإنترنت في https://eloquentjavascript.net/code. وإذا أردت أن تتعلم شيئاً من التمارين، أنصحك بالنظر في الحلول فقط بعد أن تحل التمرين، أو على الأقل بعد أن تهاجمه طويلاً وبقوة حتى تصاب بصداع خفيف.
تكرار مثلث
اكتب حلقة تجري سبعة استدعاءات لـ console.log لإخراج المثلث التالي:
#
##
###
####
#####
######
#######
قد يكون مفيداً أن تعرف أنه يمكنك إيجاد طول نص بكتابة .length بعده.
let abc = "abc";
console.log(abc.length);
// → 3
تحتوي معظم التمارين على قطعة شيفرة يمكنك تعديلها لحل التمرين. وتذكر أنه يمكنك النقر على كتل الشيفرة لتحريرها.
// اكتب شيفرتك هنا.
إظهار التلميح
يمكنك البدء ببرنامج يطبع الأعداد من 1 إلى 7، يمكنك استخلاصه بإجراء بعض التعديلات على مثال طباعة الأعداد الزوجية الوارد سابقاً في الفصل، حيث قُدمت حلقة for.
فكر الآن في التكافؤ بين الأعداد والنصوص المكونة من محارف الشباك (#). فيمكنك الانتقال من 1 إلى 2 بإضافة 1 (+= 1). ويمكنك الانتقال من "#" إلى "##" بإضافة محرف (+= "#"). وهكذا يمكن لحلك أن يتبع برنامج طباعة الأعداد عن قرب.
FizzBuzz
اكتب برنامجاً يستخدم console.log لطباعة كل الأعداد من 1 إلى 100، باستثناءين اثنين. فبالنسبة إلى الأعداد التي تقبل القسمة على 3، اطبع "Fizz" بدلاً من العدد، وبالنسبة إلى الأعداد التي تقبل القسمة على 5 (وليس على 3)، اطبع "Buzz" بدلاً من ذلك.
وعندما يعمل ذلك، عدّل برنامجك ليطبع "FizzBuzz" للأعداد التي تقبل القسمة على 3 و5 معاً (مع الاستمرار في طباعة "Fizz" أو "Buzz" للأعداد التي تقبل القسمة على واحد منهما فقط).
(هذا في الواقع سؤال مقابلات عمل زُعم أنه يستبعد نسبة كبيرة من المرشحين للمبرمجين. لذا إن حللته، فقد ارتفعت قيمتك في سوق العمل للتو.)
// اكتب شيفرتك هنا.
إظهار التلميح
مراجعة الأعداد مهمة تكرار بوضوح، واختيار ما تطبعه مسألة تنفيذ شرطي. وتذكر حيلة استخدام معامل الباقي (%) للتحقق مما إذا كان عدد يقبل القسمة على آخر (أي باقيه صفر).
وفي الإصدار الأول، هناك ثلاث نتائج محتملة لكل عدد، لذا سيتعين عليك إنشاء سلسلة if/else if/else.
والإصدار الثاني من البرنامج له حل مباشر وحل ذكي. فالحل البسيط هو إضافة «فرع» شرطي آخر لاختبار الشرط المعطى بدقة. أما الحل الذكي فابنِ نصاً يحتوي الكلمة أو الكلمات المراد إخراجها واطبع هذه الكلمة أو العدد إن لم توجد كلمة، ويمكن أن تفيد هنا من المعامل || إفادة جيدة.
رقعة الشطرنج
اكتب برنامجاً ينشئ نصاً يمثل شبكة 8×8، باستخدام محارف الأسطر الجديدة لفصل الأسطر. وفي كل موضع من الشبكة توجد إما مسافة أو محرف "#". ويجب أن تشكل المحارف رقعة شطرنج.
ويجب أن يعرض تمرير هذا النص إلى console.log شيئاً كهذا:
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
# # # #
وعندما يصبح لديك برنامج يولد هذا النمط، عرّف ارتباطاً size = 8 وعدّل البرنامج ليعمل مع أي size، فيخرج شبكة بالعرض والارتفاع المعطيين.
// اكتب شيفرتك هنا.
إظهار التلميح
يمكنك بناء النص بالبدء بنص فارغ ("") وإضافة المحارف إليه مراراً. ويُكتب محرف السطر الجديد "\n".
وللعمل مع بُعدين، ستحتاج إلى حلقة داخل حلقة. وضع أقواساً حول جسمي الحلقتين ليسهل رؤية أين يبدأ كل منهما وينتهي. وحاول إزاحة هذين الجسمين إزاحة صحيحة. ويجب أن يتبع ترتيب الحلقتين الترتيب الذي نبني به النص (سطراً سطراً، من اليسار إلى اليمين، ومن الأعلى إلى الأسفل). لذا تتعامل الحلقة الخارجية مع الأسطر، والحلقة الداخلية مع المحارف في السطر.
وستحتاج إلى ارتباطين لتتبع تقدمك. ولمعرفة ما إذا كان ينبغي وضع مسافة أو علامة شباك في موضع معين، يمكنك اختبار ما إذا كان مجموع العدادين زوجياً (% 2).
ويجب أن يحدث إنهاء السطر بإضافة محرف سطر جديد بعد بناء السطر، لذا افعل ذلك بعد الحلقة الداخلية لكن داخل الحلقة الخارجية.