سجى الحاج

تعرَّفنا على العديد من المعاملات الحسابية في المدرسة، مثل الجمع +، الضرب *، الطرح وما إلى ذلك. في هذا المقال، سنشرح معاملات أخرى لم نتعرف عليها في المدرسة بالإضافة إلى تلك المعاملات.

المعاملات.jpg

مصطلحات جديدة: أحادي، ثنائي، عامل

قبل أن نمضي قدمًا في هذا المقال، دعنا نتعرَّف على بعض المصطلحات الجديدة.

  • العوامل (operands): هي العناصر (القيمة) التي تُنفَّذ عملية المعاملات (operators) عليها. على سبيل المثال، في عملية الضرب 5 * 2، يوجد عاملان؛ العامل الأيسر هو 2 والأيمن هو 5. يطلق عليها البعض أحيانًا الاسم «وسائط» (arguments) بدلًا من «عوامل» (operands).

  • المعامل الأحادي: يكون المعامل أحاديًّا (unary) إذا كان لديه عامل (operand) واحد فقط. على سبيل المثال، عملية النفي هي عملية أحادية، إذ تعكس اشارة العدد، فيصبح سالبًا - إذا كان موجبًا + والعكس صحيح:

let x = 1;

x = -x;
alert( x ); // -1, تم تطبيق النفي الأحادي
  • المعامل الثنائي: يكون المعامل ثنائيًّا (binary) إذا كان لديه عاملان (operands). يوضح المثال التالي إشارة السالب التي في المثال السابق عندما تكون في المعامل الثنائي:
let x = 1, y = 3;
alert( y - x ); // 2, معامل الطرح الثنائي يطرح القيم

تقنيًّا، نتحدث هنا عن نوعين من المعاملات لإشارة واحدة - هما: النفي الأحادي (العامل الفردي: عكس الإشارة) والطرح الثنائي (عاملان: عملية طرح)، لذا يجب التفريق بينهما.

وصل سلاسل نصية عبر المعامل +

الآن، سنتعرَّف على ميِّزات خاصة بمعاملات JavaScript تتجاوز تلك التي تعلمناها في المدرسة.

المتعارف عليه أنَّ معامل الجمع + يجمع الأرقام. لكن إذا تم تطبيق معامل الجمع + على سلاسل نصية، فإنه يُوصل هذه السلاسل النصية مع بعضها بعضًا ويضعها في سلسلة نصية واحدة.

let s = "my" + "string";
alert(s); // mystring

لاحظ أنه يتم تحويل العدد إلى سلسلة نصية عن طريق وضعه داخل علامات الاقتباس ("") ويعامل آنذاك معاملة السلسلة النصية. ولاحظ أيضًا إذا كان أحد العاملين (operands) عبارة عن سلسلة نصية، فسيُحوَّل الآخر تلقائيًا إلى سلسلة نصية. إليك المثال التالي:

alert( '1' + 2 ); // "12"
alert( 2 + '1' ); // "21"

كما رأيت، لا يهم ما إذا كان العامل الأول عبارة عن سلسلة نصية أم الثاني. القاعدة بسيطة: إذا كان أحد العاملين عبارة عن سلسلة نصية، فسيُحوَّل الآخر إلى سلسلة نصية أيضًا.

لكن انتبه! المعاملات تبدأ من اليسار إلى اليمين. فإذا كان هناك عددان متبوعان بسلسلة نصية، فستُجمَع الأعداد قبل تحويلها إلى سلسلة نصية:

alert(2 + 2 + '1' ); // "أي أن الناتج "41" وليس "221

خاصية وصل السلاسل النصية وجمعها في سلسلة واحدة (String concatenation) ميزة خاصة بمعامل الجمع +، بينما تعمل المعاملات الحسابية الأخرى مع الأعداد فقط. فعلى سبيل المثال، الطرح والقسمة:

alert( 2 - '1' ); // 1
alert( '6' / '2' ); // 3

خاصية التحويل العددي

يوجد لمعامل الجمع + صيغتان: الأول معامل الجمع الثنائي كما في خاصية دمج السلاسل النصية، والثاني معامل الجمع الأحادي كما في هذه الخاصية.

معامل الجمع الأحادي أو بمعنى آخر، معامل الجمع + المطبق على قيمة واحدة، لا يُحدث تغييرًا على الأعداد. ولكن إذا لم يكن العامل عددًا، فإن عملية الجمع الأحادي تحوّله إلى عدد. اطلع على المثال التالي:

// لا تأثير على الأعداد
let x = 1;
alert( +x ); // 1

let y = -2;
alert( +y ); // -2

// تحويل غير الأعداد
alert( +true ); // 1
alert( +"" );   // 0

يقوم هذا المعامل هنا مقام الدالة Number‎، لكنه أقصر وأبسط.

تنشأ الحاجة لتحويل السلاسل النصية إلى أعداد في كثير من الأحيان. على سبيل المثال، إذا كنا نحصل على القيم من قالب HTML، فهي غالبًا ما تكون عبارة عن سلاسل نصية. ماذا لو أردنا دمجها؟ معامل الجمع الثنائي سيضيفها كسلاسل نصية:

let apples = "2";
let oranges = "3";

alert( apples + oranges ); // "23", معامل الجمع الثنائي يدمج السلاسل النصية

إذا كنا نريد أن نعاملها كأعداد، نحتاج إلى تحويلها ثم دمجها:

let apples = "2";
let oranges = "3";

// كلا القيمتين حُولتا إلى أعداد قبل تطبيق معامل الجمع الثنائي
alert( +apples + +oranges ); // 5

// بصيغة أطول
// alert( Number(apples) + Number(oranges) ); // 5

من وجهة نظر عالم رياضيات، تبدو كثرة معاملات الجمع غريبة. ولكن من وجهة نظر المبرمج، لا يوجد شيء مميز: يتم تطبيق معاملات الجمع الأحادي أولاً لتحويل السلسلة النصية إلى عدد، ثم يجمع معامل الجمع الثنائي العددين الناتجين.

لماذا يتم تطبيق معاملات الجمع الأحادية على القيم قبل الثنائية؟ هذا يعود للأولوية الأعلى في التنفيذ كما سنرى بعد قليل.

ترتيب أولوية المعاملات

إذا كان في المعادلة أكثر من معامل واحد، يتم تحديد ترتيب التنفيذ حسب أولوية هذه المعاملات. في المدرسة، تعلمنا جميعًا أنه يجب حساب الضرب في المعادلة1 + 2 * 2 قبل الجمع، هذا تحديدًا ما يسمى أولوية العملية الرياضية. أي أن عملية الضرب لها أولوية أعلى من الجمع.

تتخطى الأقواس أولوية أي معامل، لذلك إذا لم نكن راضين عن أولوية المعاملات (operations) في المعادلة، يمكننا استخدام الأقواس لتغييرها. على سبيل المثال: (1 + 2) * 2.

هناك العديد من المعاملات في JavaScript. كل معامل لديه رقم في ترتيب الأولوية، والمعامل صاحب الأولوية الأعلى يُنفَّذ أولًا. إذا كانت الأولوية لمجموعة معاملات في نفس الترتيب، يكون التنفيذ من اليسار إلى اليمين.

إليك مقتطف من جدول الأولوية (لست بحاجة إلى حفظ ذلك، لكن لاحظ أن المعاملات الأحادية أعلى أولوية من المعاملات الثنائية المقابلة).

الأولوية اسم المعامل إشارة المعامل
... ... ...
16 الجمع الأحادي +
16 النفي الأحادي -
14 الضرب *
14 القسمة /
13 الجمع الثنائي +
13 الطرح الثنائي -
... ... ...
3 الإسناد =
... ... ...

كما ترى، معامل الجمع الأحادي له الأولوية 16 وهي أعلى من 13 لمعامل الجمع الثنائي. لهذا السبب، يُنفَّذ معامل الجمع الأحادي أولًا في المعادلة +apples + +oranges الذي يمثِّل عملية التحويل قبل عملية الجمع وهذا هو المطلوب.

معامل الإسناد =

لاحظ أنَّ معامل الإسناد = مدرجٌ في جدول الأولوية الخاص بالمعاملات مع أولوية منخفضة للغاية وهي 3. لهذا السبب، عندما نسند قيمة لمتغير، مثل x = 2 * 2 + 1، تُنفَّذ العمليات الحسابية حسب أولويتها ثمَّ تُسنَد القيمة الناتجة (5) إلى المتغيِّر x وتُخزَّن فيه.

let x = 2 * 2 + 1;

alert( x ); // 5

من الممكن سَلسَلة معاملات الإسناد:

let a, b, c;

a = b = c = 2 + 2;

alert( a ); // 4
alert( b ); // 4
alert( c ); // 4

يتم تنفيذ سَلسَلة معاملات الإسناد من اليمين إلى اليسار. أولاً، يُقيَّم التعبير 2 + 2 الموجود في أقصى اليمين ثم تُسنَد القيمة الناتجة عنه إلى المتغيرات الموجودة على اليسار:c، وb و a. في النهاية، تشترك جميع تلك المتغيرات في القيمة النهائية الناتجة.

معامل الإسناد = يُرجع قيمة

المعامل دائما يرجع قيمة. هذا واضح في معامل الجمع + أو الضرب *، ويتبع معامل الإسناد = أيضًا هذه القاعدة. فالاستدعاء x = Value يُخزِّن القيمة في x ثم يرجعها.

يوضح المثال التالي كيفية الاستفادة من هذه الميزة باستخدام القيمة الناتجة عن عملية الإسناد في معادلة أخرى أكثر تعقيدًا، وبذلك نضرب عصفورين بحجر واحد:

let a = 1;
let b = 2;

let c = 3 - (a = b + 1);

alert( a ); // 3
alert( c ); // 0

في المثال أعلاه، تكون نتيجة a = b + 1 هي القيمة التي جرى إسنادها إلىa أي 3، ثم يتم طرح من هذه القيمة 3.

شيفرة مضحكة، أليس كذلك؟! لكن يجب أن نفهم كيف تعمل لأنه في بعض الأحيان نراها في مكتبات خارجية كتبها آخرون، ولا يُفضل إطلاقًا أن نكتب مثل هذه الشيفرات، لأنَّ هذه الحيل لا تجعل من الشيفرة أكثر وضوحًا أو أكثر قابلية للقراءة.

معامل باقي القسمة %

معامل باقي القسمة %، لا يرتبط بالنسب المئوية. الناتج من هذه المعادلة a % b هو العدد المتبقي من قسمة العدد الصحيح a على العدد الصحيحb. إليك مثال عنه:

alert( 5 % 2 ); // 1 (هو الباقي من قسمة 5 على 2)
alert( 8 % 3 ); // 2 (هو الباقي من قسمة 8 على 3)
alert( 6 % 3 ); // 0 (هو الباقي من قسمة 6 على 3)

معامل القوة **

معامل القوة ** هو إضافة حديثة لـ JavaScript. ليكن لدينا الرقم الطبيعي b، فيكون ناتج العملية a ** b هو الرقم a مضروبًا في نفسه عدد b من المرات:

alert( 2 ** 2 ); // 4  (2 * 2)
alert( 2 ** 3 ); // 8  (2 * 2 * 2)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2)

يعمل معامل القوة مع الأعداد غير الصحيحة أيضًا مثل:

alert( 4 ** (1/2) ); // 2 (معامل القوة ½ هو نفس الجذر التربيعي)
alert( 8 ** (1/3) ); // 2 (معامل القوة ⅓ هو نفس الجذر التكعيبي)

معاملات الزيادة ++ والنقصان --

تُعدُّ الزيادة على العدد أو إنقاصه بمقدار 1 من أكثر العمليات العددية شيوعًا. لذلك، هناك معاملات خاصة لهذا الغرض:

  • معامل الزيادة ++ يزيد على قيمة المتغير مقدار 1:
let counter = 2;
counter++; // ببساطة counter = counter + 1 هو اختصار للعملية
alert( counter ); // 3
  • معامل النقصان -- ينقص من قيمة المتغير مقدار 1:
let counter = 2;
counter--;      // counter = counter - 1 هو اختصار للعملية
alert( counter ); // 1

تنبيه: معاملات الزيادة / النقصان خاصة بالمتغيرات فقط، ومحاولة تطبيقها على قيم عددية مثل ‎5++‎ سيعطي خطأ.

يمكن وضع هذين المعاملين ++، -- قبل المتغير أو بعده.

  • عندما يتبع المعامل المتغير، هذا ما يسمى «النموذج اللاحق» (postfix form) مثل counter++.
  • وبالمثل، «النموذج السابق» (prefix form)، عندما يأتي المعامل قبل المتغير مثل ++counter.

كلا النموذجين يقومان بالعمل نفسه وهو الزيادة على قيمة المتغير counter بمقدار 1. إذن، هل هناك فرق؟ نعم، ولكن لا يمكننا رؤيته إلا إذا استخدمنا القيمة المعادة من العملية تلك.

دعني أوضح لك أكثر. كما نعلم، جميع المعاملات ترجع قيمة وكذلك أيضًا معاملات الزيادة/النقصان. يعيد النموذج السابق القيمة الجديدة بينما يعيد النموذج اللاحق القيمة القديمة (قبل الزيادة / النقصان).

إليك مثال على ذلك ليتضح كل الفرق وضوح الشمس:

let counter = 1;
let a = ++counter; // (*)

alert(a); // 2

في السطر المُميّز بالنجمة (*)، يزيد النموذج السابق ++counter قيمة المتغير counter ويعيد القيمة الجديدة الناتجة وهي 2. لذا، تُظهر الدالة alert القيمة 2.

الآن، دعني استخدم النموذج اللاحق بدلًا من النموذج السابق لمعرفة الفرق:

let counter = 1;
let a = counter++; // (*)
alert(a); // 1

في السطر المُميّز بالنجمة (*)، يزيد النموذج اللاحق counter++ قيمة المتغير counter لكنه يعيد القيمة القديمة (قبل الزيادة). لذا، تُظهِر الدالة alert القيمة 1.

مختصر القول:

  • إذا لم يتم استخدام القيمة الناتجة من معاملي الزيادة والانقاص، فلا يوجد فرق في النموذج الذي يتم استخدامه سواءً سابق أو لاحق:
let counter = 0;
counter++;
++counter;
alert( counter ); // 2, كلا السطرين يؤديان نفس الغرض
  • أمَّا إذا كنت ترغب في زيادة قيمة المتغير واستخدامها مباشرةً، فأنت بحاجة إلى استعمال النموذج السابق:
let counter = 0;
alert( ++counter ); // 1
  • وإذا كنت ترغب في زيادة قيمة المتغير ولكنك تريد استخدام قيمته السابقة، فاستعمل النموذج اللاحق:
let counter = 0;
alert( counter++ ); // 0

معاملات زيادة / نقصان داخل معاملات اخرى:

يمكن استخدام معاملات زيادة / نقصان (++ / --) داخل المعادلات أيضًا، وأولويتها في التنفيذ أعلى من معظم المعاملات الحسابية الأخرى. إليك المثال التالي:

let counter = 1;
alert( 2 * ++counter ); // 4

وازنه مع المثال التالي:

let counter = 1;
alert( 2 * counter++ ); // 2, لأن counter++ يرجع القيمة القديمة

رغم أنَّه مقبول من الناحية التقنية، إلا أنَّ هذه الشيفرة عادة ما تجعل الشيفرة الكلية صعبة القراءة. اتباع نموذج «سطر واحد يفعل أشياء متعددة» ليس جيدًا ويولد شيفرة صعبة القراءة والتنقيح.

أثناء تَصفُّح الشيفرة سريعًا يمكن أن لا تنتبه على شيء مثل counter++، أي لن تلاحظ أنَّ قيمة المتغير قد زادت. فتخيل ما سيحصل إن حدث خطأ ما مع وجود مئات مثل تلك الأسطر التي تنجز عدة أشياء سويةً! أنصحك باستخدام أسلوب «سطر واحد - إجراء واحد» (one line – one action):

let counter = 1;
alert( 2 * counter );
counter++;

المعاملات الثنائية

تتعامل المعاملات الثنائية (Bitwise operators) مع العوامل كأعداد صحيحة بحجم 32 بت وتعمل على مستوى تمثيلها الثنائي (binary representation). هذه العوامل ليست خاصة بـ JavaScript، وهي مدعومة في معظم لغات البرمجة.

إليك قائمة بالمعاملات الثنائية (Bitwise operators):

  • المعامل AND (&)
  • المعامل OR (|)
  • المعامل XOR (^)
  • المعامل NOT (~)
  • معامل الإزاحة نحو اليسار LEFT SHIFT (>>)
  • معامل الإزاحة نحو اليمين RIGHT SHIFT (<<)
  • معامل الإزاحة نحو اليمين مع إدخال أصفار ZERO-FILL RIGHT SHIFT (<<<)

نادرًا ما تحتاج إلى استخدام مثل هذه المعاملات. وتحتاج لفهمها إلى الخوض في التمثيل الثنائي للأعداد (الصيغة الثنائية المقابلة للصيغة العشرية المفهومة للبشر) وليس من الجيد القيام بذلك الآن، خاصة أننا لسنا بحاجة إلى ذلك في هذا الوقت. إذا كنت فضولي يمكنك قراءة توثيق المعاملات الثنائية في JavaScript في موسوعة حسوب، وهذا كافيًّا الآن. وسنشرحها عمليًّا عندما تنشأ حاجة حقيقية لها في مراحل متقدمة.

معاملات التعديل المباشر على المتغير (Modify-in-place)

غالبًا ما نحتاج إلى تطبيق معامل على متغير ما وتخزين القيمة الجديدة في المتغير نفسه. انظر مثلًا إلى المثال التالي:

let n = 2;
n = n + 5;
n = n * 2;

يمكن اختصار الشيفرة السابقة باستخدام ‎+=‎ و ‎*=‎:

let n = 2;
n += 5; // (n = n + 5 وهذا يماثل) n = 7 أي تصبح
n *= 2; // (n = n * 2 وهذا يماثل) n = 14 تصبح الآن
alert( n ); // 14

معاملات التعديل والإسناد (modify-and-assign) متاحة لجميع المعاملات الحسابية والثنائية مثل: ‎/= و ‎-= وتملك هذه المعاملات الأولوية نفسها التي لدى معامل الإسناد العادي =، لذا فهي تُنفَّذ بعد معظم المعاملات الأخرى:

let n = 2;
n *= 3 + 5;
alert( n ); // 16  (n *= 8 القسم الأيمن يُقيَّم أولًا وهذا يماثل)

معامل الفاصلة ,

معامل فاصلة (Comma) , هو واحد من أندر المعاملات وأكثرها غرابة. في بعض الأحيان يتم استخدامه لكتابة شيفرة أقصر، لذلك نحن بحاجة إلى التعرف عليه لفهم ذلك.

يتيح لنا معامل الفاصلة تقييم العديد من المعادلات عند فصلها بفاصلة ,. يجري تقييم كل قسم منها ولكن يتم إرجاع نتيجة آخر واحد فقط. تفحَّص المثال التالي:

let a = (1 + 2, 3 + 4);
alert( a ); // 7 (الناتج من جمع 3+4)

هنا، يُقيَّم القسم الأول من المعادلة 1+2 ويُتجاهَل قيمته ثم بعد ذلك يُقيَّم القسم الثاني 3+4 وتعاد قيمته كنتيجة نهائية.

معامل الفاصلة له أولوية منخفضة جدًا

يرجى ملاحظة أن معامل الفاصلة له أولوية منخفضة للغاية، أقل من معامل الإسناد =، لذلك الأقواس مهمة في المثال أعلاه. بدون الأقواس: a = 1 + 2 ، 3 + 4 يُنفَّذ معامل الجمع + أولاً، فتصبح المعادلة a= 3,7، ثم يُنفَّذ معامل الإسناد = فتصبح a=3، وأخيرًا لا يُطبَّق أي معامل على العدد الموجود بعد الفاصلة، أي يُتجاهَل العدد7.

لماذا نحتاج إلى معامل يتجاهل كل القيم باستثناء قيمة الجزء الأخير؟ في بعض الأحيان، يستخدمه المبرمجون في بنيات أكثر تعقيدًا لوضع العديد من الإجراءات في سطر واحد. قد تكون أبسط تلك البنيات المعقدة هي بنية الحلقية التكرارية التالية:

// ثلاث معاملات في سطر واحد
for (a = 1, b = 3, c = a * b; a < 10; a++) {
 ...
}

تُستخدَم هذه الحيل في العديد من أطر JavaScript. لكنها في العادة لا تحسّن قابلية القراءة للشيفرة، لذلك يجب عليك التفكير جيدًا قبل استخدامها.

تمارين

النموذج السابق والنموذج اللاحق

الأهمية: 5

ما هي القيم النهائية للمتغيرات a ،b ،c ،d بعد تنفيذ الشيفرة التالية؟

let a = 1, b = 1;
let c = ++a; // ?
let d = b++; // ?

الحل:

الإجابة هي:

  • a = 2
  • b = 2
  • c = 2
  • d = 1
let a = 1, b = 1;

alert( ++a ); // 2, النموذج السابق يُرجع القيمة الجديدة
alert( b++ ); // 1, النموذج اللاحق يُرجع القيمة القديمة

alert( a ); // 2, تتم الزيادة مرة واحدة
alert( b ); // 2, تتم الزيادة مرة واحدة

نتيجة عملية الإسناد

الأهمية: 3

ما هي قيم المتغيرين a و x بعد تنفيذ الشيفرة التالية؟

let a = 2;
let x = 1 + (a *= 2);

الحل

الإجابة هي:

  • a = 4 (مضروبًا في العدد 2)
  • x = 5 (عبارة عن مجموع العدد 1 والعدد 4)

ترجمة -وبتصرف- للفصل operators من كتاب The JavaScript Language

انظر أيضًا





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن