EduFlow
أساسيات JavaScript
⁦0%⁩

الوحدة 1 · الخطوات الأولى

الأرقام والعوامل والمقارنات

17 دقراءة 4 دبيئة تجريب الكود

الدرس في لمحة

تُجري العمليات الحسابية على أرقام JavaScript، وتتجنّب مفاجآت الأعداد العشرية بتخزين المال سنتاتٍ صحيحة، وتقارن القيم بأمان باستخدام === بدلًا من ==.

  1. 1العمليات الحسابية بالطريقة المعتادة
  2. 2لماذا لا تساوي 0.1 + 0.2 القيمة 0.3
  3. 3حين تلصق + بدلًا من أن تجمع
  4. 4مقارنة القيم

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

اطلب من JavaScript أن تجمع ثمن قهوة وغداء وتذكرة حافلة، وستعطيك جوابًا خاطئًا بفارق ضئيل:

JavaScript
console.log(4.1 + 12.2 + 2.7);

توقّعت 19 فحصلت على 18.999999999999996. هذا ليس خطأً في JavaScript؛ فمعظم لغات البرمجة تفعل الشيء نفسه. وحين تفهم السبب، تستطيع أن تصمّم Pocket بحيث لا يلدغك هذا أبدًا.

العمليات الحسابية بالطريقة المعتادة

تعمل العوامل الحسابية كما تتمنّى، مع أولوية العمليات المعتادة: * و/ و% تُنفَّذ قبل + و-، والأقواس تتقدّم على كل شيء.

JavaScript
console.log(2 + 3 * 4);    // multiplication first
console.log((2 + 3) * 4);  // parentheses first
console.log(17 % 5);       // remainder after dividing 17 by 5
console.log(2 ** 10);      // 2 to the power of 10

% (باقي القسمة) أنفع مما تبدو: n % 2 === 0 تخبرك إن كان الرقم زوجيًا، وminutes % 60 تعطيك الدقائق المتبقية بعد الساعات الكاملة.

حين تحدّث متغيّرًا انطلاقًا من قيمته نفسها، توجد اختصارات. total += 450 تعني total = total + 450، والنمط نفسه يعمل مع -= و*= و/=. أما count++ فتضيف واحدًا، وستراها في حلقات التكرار.

لماذا لا تساوي 0.1 + 0.2 القيمة 0.3

لدى JavaScript نوع واحد للأرقام، يُستخدم للأعداد الصحيحة والعشرية معًا. وفي الخفاء يُخزَّن كل رقم بالنظام الثنائي في 64 بتًا، وفق معيار اسمه IEEE 754. الأعداد الصحيحة تتّسع بلا مشكلة، أما كثير من الأعداد العشرية فلا: فالعدد 0.1 بالنظام الثنائي كسر يتكرّر إلى ما لا نهاية، مثل 1/3 بالنظام العشري (0.3333…). يضطر الحاسوب إلى قطعه في مكان ما، فيخزّن أقرب قيمة يستطيعها. اجمع بضعًا من هذه القيم “شبه الصحيحة” فيصبح الخطأ مرئيًا.

JavaScript
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log(4.1 * 100);

الخطأ ضئيل جدًا، نحو جزء واحد من عشرة كوادريليون، لذلك لا يهم أبدًا في قياس المسافات أو الرسم على الشاشة. لكنه يهم في المال. فالمجموع الذي يُفترض أن يكون 19 لكنه مخزّن على أنه 18.999999999999996 يفشل في فحص المساواة، ويُقرَّب في الاتجاه الخاطئ على الإيصال، وينحرف أكثر مع كل مصروف تضيفه. وتقريب الرقم عند عرضه يُخفي العَرَض بينما يبقى الرقم المخزّن خاطئًا.

مع المال، الحل بسيط، وهو ما تفعله شركات الدفع: خزّن المبالغ سنتاتٍ صحيحة. الأعداد الصحيحة دقيقة حتى Number.MAX_SAFE_INTEGER، أي نحو 9 كوادريليون، وهذا أكثر من كافٍ لمتتبّع مصاريف.

JavaScript
const coffee = 410;   // $4.10 in cents
const lunch = 1220;
const bus = 270;
const total = coffee + lunch + bus;
console.log(total);         // exact
console.log(total / 100);   // convert to dollars only when you display it

من هذا الدرس فصاعدًا، يُخزَّن كل مبلغ في Pocket بالسنتات. وحين يصل مبلغ بالدولار، مثل 4.1، حوّله مرة واحدة باستخدام Math.round(4.1 * 100). لـ Math.round أهمية هنا، لأن 4.1 * 100 تساوي 409.99999999999994، والتقريب يُرجعها إلى 410.

حين تلصق + بدلًا من أن تجمع

القيم التي تُكتب في صفحة ويب تصل دائمًا نصوصًا، حتى لو بدت أرقامًا. ولـ + وظيفتان: جمع الأرقام ولصق النصوص. فإن كان أحد الطرفين نصًا، فإنها تلصق.

JavaScript
const typed = "5";
console.log(typed + 3);          // joins: "53"
console.log(typed - 3);          // only maths makes sense, so 2
console.log(Number(typed) + 3);  // convert first: 8
console.log(Number("12abc"));    // not a number: NaN

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

حين يفشل التحويل تحصل على NaN، أي “ليس رقمًا” (not a number)، والمربك أن نوعها number. وNaN معدية: أي عملية حسابية معها تعطي NaN. افحصها باستخدام Number.isNaN(value)، لأن NaN === NaN تساوي false.

وقريبتها هي Infinity. القسمة على صفر لا تُوقف برنامج JavaScript كما تفعل في الآلة الحاسبة: 5 / 0 تساوي Infinity، و-5 / 0 تساوي -Infinity. يبدو هذا غير مؤذٍ حتى يحسب Pocket “متوسط الإنفاق اليومي” في أول الشهر وعدد الأيام المسجّلة صفر. لا NaN ولا Infinity ترمي خطأً، فيستمر برنامجك في العمل ويعرض كلامًا بلا معنى. والوقاية واحدة لكليهما: افحص مدخلاتك قبل أن تحسب.

مقارنة القيم

عوامل المقارنة تُنتج قيمة منطقية (boolean): > و< و>= و<=، ونوعا المساواة.

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

التعبير == ===
"5" و5 true false
"" و0 true false
null وundefined true false
"0" وfalse true false

استخدم === و!== في كل مكان. وإن كان النوعان قد يختلفان، فحوّل صراحةً أولًا ثم قارن. عندها يقول كودك ما يعنيه بالضبط، ولا يحتاج أحد إلى حفظ جدول تحويلات.

JavaScript
const budgetCents = 2500;
const spentCents = 2200;
const isOverBudget = spentCents > budgetCents;
console.log(isOverBudget, typeof isOverBudget);
console.log(spentCents === 2200, "2200" === 2200);

بعد أن صارت المبالغ دقيقة والمقارنات آمنة، الخطوة التالية أن تعرضها بشكل جميل: النصوص، وكيف تبني نصًا مقروءًا مثل “Lunch: $12.20”.

أهم النقاط

  • لدى JavaScript نوع واحد للأرقام الصحيحة والعشرية معًا، والأعداد العشرية تُخزَّن بالنظام الثنائي، لذلك لا تساوي 0.1 + 0.2 القيمة 0.3 بالضبط.
  • خزّن المال سنتاتٍ صحيحة (integers)، ولا تحوّله إلى دولارات إلا عند العرض؛ فالحساب بالأعداد الصحيحة دقيق تمامًا.
  • + مع نص تُلصق النصوص بدلًا من أن تجمع، لذلك حوّل المدخلات باستخدام Number() قبل أي عملية حسابية.
  • قارن دائمًا باستخدام === و!==؛ فـ == تحوّل الأنواع أولًا وتعطي نتائج مثل أن "" == 0 صحيحة.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

يكتب المستخدم 5 في حقل نصي، فيستلم كودك النص "5". ما ناتج "5" + 3؟

السؤال 2 من 4

يخزّن Pocket القهوة بقيمة 4.1 دولار والغداء بقيمة 12.2. لماذا يُعدّ تخزين 410 و1220 سنتًا تصميمًا أفضل؟

السؤال 3 من 4

أيّ مقارنة نتيجتها true؟

السؤال 4 من 4

ما القيمة التي يتركها let n = 4; n += 2; n *= 3; في n؟

تدريب

بيئة تجريب الكود

المجموع بالسنتات، بدقة

يجمع كود البداية ثلاثة مبالغ بالدولار ونصًا مكتوبًا، فتكون النتيجة فوضى. أصلح totalCents بحيث يكون المجموع الدقيق بالسنتات الصحيحة للمبالغ الأربعة كلها (حوّل كل مبلغ إلى سنتات باستخدام Math.round(amount * 100)، وحوّل typedAmount إلى رقم أولًا). ثم صرّح بـ isOverBudget، وهي قيمة منطقية تساوي true حين يكون totalCents أكبر من budgetCents، وبـ leftCents، أي الميزانية ناقص المجموع.

// Three expenses, still in dollars
const coffee = 4.1;
const lunch = 12.2;
const bus = 2.7;
// A fourth amount, typed into a text box, so it arrived as a string
const typedAmount = "3";

const budgetCents = 2500;

// Fix this: it should be the exact total in whole cents
const totalCents = coffee + lunch + bus + typedAmount;

// Declare isOverBudget and leftCents below
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

الـ console

(لا مخرجات)

الفحوص

  • totalCents رقم، وليس نصًا
  • totalCents يساوي 2200 بالضبط
  • isOverBudget هي القيمة المنطقية false
  • leftCents يساوي 300