نوعان من التحويل في جافا سكريبت
جافا سكريبت لغة متساهلة مع الأنواع. حين يستقبل أحد المعاملات قيمة من نوع "غير مناسب"، لا ترمي اللغة خطأً، بل تقوم بتحويل القيمة. هذا التحويل يُسمّى type coercion، وينقسم إلى نوعين:
- التحويل الصريح (Explicit) — أنت من يطلبه بنفسه:
Number("42"),String(99),Boolean(value). - التحويل الضمني (Implicit) — يحدث تلقائيًا بفعل أحد المعاملات دون أن تكتب أي دالة تحويل:
"5" - 2,"" == 0,if (value).
النوعان في النهاية يستدعيان نفس قواعد التحويل الداخلية، والفارق الوحيد هو: هل أنت من قرّر التحويل، أم أن اللغة قرّرته نيابةً عنك؟ ومعظم النتائج المُربِكة في جافا سكريبت — مثل "5" + 1 === "51" و [] == false و null == undefined — مصدرها التحويل الضمني الذي يباغتك من حيث لا تحتسب.
النموذج الذهني: لمّا تكتب Number(...) أو String(...)، أنت تعرف بالضبط شو طالب من المحرّك. أما لمّا ما تكتبها، فكل معامل (operator) إله رأيه الخاص بكيفية التحويل، وهون بالضبط بتسكن معظم الباغات.
الأنواع الثلاثة المستهدفة في التحويل
تحويل الأنواع في جافا سكريبت بيصبّ دايمًا باتجاه واحد من ثلاثة أنواع بدائية: string، أو number، أو boolean. (في نوع رابع وهو bigint، بس ما بيصير التحويل إليه تلقائيًا من أنواع تانية.) وباقي قواعد التحويل في javascript كلها بتتفرّع من سؤال واحد: لأي نوع من هدول الثلاثة بيحاول المعامل يوصل؟
التحويل إلى نص في جافا سكريبت متسامح جدًا — فكل قيمة لها تمثيل نصي. لكن انتبه أن الكائنات (objects) تتحول إلى النص عديم الفائدة "[object Object]"، ولهذا السبب عندما تطبع كائنًا مدموجًا مع نص ("user: " + user) نادرًا ما ترى ما كنت تتوقعه. استخدم JSON.stringify أو القوالب النصية (template literals) مع تحديد الحقول التي تريدها بدلًا من ذلك.
التحويل إلى رقم في جافا سكريبت
التحويل إلى رقم أكثر صرامة. لا بد أن يكون النص على هيئة رقم فعلًا، وإلا ستحصل على NaN:
المفاجآت الموضوعة بين قوسين هي التي تستحق أن تحفظها جيدًا:
- السلسلة الفارغة والسلاسل التي تحتوي على مسافات فقط تتحول إلى
0، وليس إلىNaN. - القيمة
nullتتحول إلى0، أماundefinedفتتحول إلىNaN. - المصفوفة الفارغة تتحول إلى
0، والمصفوفة ذات العنصر الواحد تتحول إلى قيمة ذلك العنصر نفسه، بينما المصفوفة التي تحتوي على أكثر من عنصر تتحول إلىNaN.
وإذا أردت استخراج رقم من نص يحتوي على رموز إضافية، فاستخدم parseInt أو parseFloat بدلًا من Number:
مرر دائمًا الأساس (10) إلى parseInt. لأنك لو أهملته، فالسلاسل التي تبدأ بـ "0x" ستُفسَّر كأرقام ست عشرية، وهذا نادرًا ما يكون مقصدك.
التحويل إلى Boolean
التحويل إلى Boolean هو الأبسط بين الأنواع الثلاثة. هناك قائمة قصيرة من القيم تتحول إلى false، وكل ما عداها يتحول إلى true.
القيم falsy هي:
false0,-0,0n""(سلسلة فارغة)nullundefinedNaN
هذه هي القائمة كاملة. أي قيمة أخرى — بما فيها "false" و "0" و [] و {} — تُعدّ truthy.
نتائج المصفوفة الفارغة والكائن الفارغ تُربك كثيرًا من القادمين من بايثون، حيث المجموعات الفارغة تُعتبر falsy. أما في جافا سكريبت فهي truthy — فإذا أردت أن تتحقق من "هل هذه المصفوفة فارغة؟"، افحص arr.length === 0 صراحةً.
التحويل إلى قيمة منطقية يحدث في كل مرة تظهر فيها قيمة في موضع يتوقع قيمة منطقية: if (...)، وwhile (...)، والعامل الثلاثي ? :، والعوامل المنطقية && و|| و!.
عامل + حالة خاصة
معظم العوامل الحسابية تُجبر معاملاتها على التحول إلى أرقام، لكن + مختلف — إذا كان أيّ طرف من طرفيه نصًا، فإن + يقوم بدمج النصوص (string concatenation)، وفي غير ذلك يُجري جمعًا عدديًا.
التقييم يتم من اليسار إلى اليمين. فالعبارة 1 + 2 + "3" تحسب أولًا 1 + 2 = 3، ثم 3 + "3" = "33". أما "1" + 2 + 3 فتبدأ كسلسلة نصية وتبقى كذلك: تصير "12"، ثم "123".
ولهذا السبب بالذات يكون بناء النصوص باستخدام + هشًّا وعرضة للأخطاء. القوالب النصية (Template literals) لا تعاني من هذه المشكلة:
يقوم القالب الحرفي (template literal) بتقييم count + 1 كتعبير رقمي مستقل، ثم يُدرج الناتج داخل النص. وبهذا نتخلّص من أي تحويل ضمني غير متوقّع.
التحويل الصريح أفضل من الضمني
عندما تحتاج فعلاً إلى تحويل نوع ما، فاكتب ذلك بوضوح. كلفة بضعة أحرف إضافية مقابل إزالة أي غموض عن القارئ التالي للكود:
نفس الكلام ينطبق على القيم المنطقية. الصيغة !!value شغّالة ومنتشرة بين المطورين، لكن Boolean(value) تعبّر بوضوح عن المقصود:
قاعدة منطقية: استخدم التحويل الصريح في منطق التطبيق، واحتفظ بالصيغ المختصرة (+x, !!x) للمواضع التي تكون فيها الإيجاز ضروريًا ويكون القصد واضحًا من السياق.
عامل == يعتمد على التحويل الضمني بشكل كبير
عوامل المساواة هي المصدر الأكبر للمفاجآت المتعلقة بالتحويل الضمني. فعامل == يقوم بتحويل القيم قبل المقارنة، بينما === لا يفعل ذلك — وهنا يكمن الفرق بين == و === في javascript.
كل true في الأمثلة السابقة هو نتيجة سلسلة تحويلات متعددة الخطوات، ونادراً ما يستطيع أي مطور استذكارها عن ظهر قلب. وهنا تكمن المشكلة: الكود الذي يعمل بالصدفة هو كود سينكسر لاحقاً. سنتناول القواعد الكاملة للمساواة في الصفحة التالية، لكن الخلاصة الآن: استخدم === افتراضياً، ولا تلجأ إلى == إلا في حالة واحدة محددة وهي التعبير x == null (الذي يلتقط كلاً من null و undefined معاً).
نجمع كل الخيوط معاً
إليك مثالاً عملياً يوضح متى يكون تحويل الأنواع مفيداً ومتى يتحول إلى كارثة:
لاحظ الاستدعاء الثاني. الدالة Number("") تُرجع 0 وليس NaN، وبالتالي فإن parsePrice("") تُرجع 0، وهذه على الأرجح ليست النتيجة التي يتوقعها من يستخدم هذه الدالة. إذا كنت تريد رفض المدخلات الفارغة، أضف تحققًا صريحًا لذلك:
معرفة أي القيم تتحوّل إلى 0 وأيّها إلى NaN هي بالضبط النوع من المعلومات اللي بيقيك من باغات غبية وصعبة التتبّع لاحقًا.
خلاصة الدرس
- تحويل الأنواع في جافا سكريبت بيحوّل القيمة إلى نص أو رقم أو قيمة منطقية حسب العامل المستخدم.
- عامل
+مع أي نص بيتحوّل إلى دمج نصوص (concatenation)، أما باقي العوامل الحسابية فبتحوّل الطرفين إلى أرقام. - القيم الـ falsy عددها محدود ومعروف — احفظها. وأي حاجة تانية تُعتبر truthy، بما في ذلك
[]و{}. Number("")بيساوي0، وNumber([])بيساوي0، وNumber(null)بيساوي0— لكنNumber(undefined)بيساويNaN. الفروق دي بتظهر في باغات حقيقية.- استخدم
Number(x)وString(x)وBoolean(x)بدل الاعتماد على الحيل الضمنية الذكية. نفسك في المستقبل هتشكرك.
التالي: عوامل المساواة
كل ما يفعله التحويل الضمني داخل عمليات المقارنة موجود في ==. الصفحة الجاية هنقارن بين == و === و Object.is، ونشوف الحالة الوحيدة اللي لسه فيها == مفيد، وليه أدوات الـ linter بتحذّر من الصيغة المرنة افتراضيًا.
الأسئلة الشائعة
ما المقصود بتحويل الأنواع (Type Coercion) في JavaScript؟
تحويل الأنواع يعني أن JavaScript تقوم تلقائيًا بتحويل قيمة من نوع إلى آخر: رقم يتحول إلى نص، نص إلى رقم، أو أي قيمة إلى قيمة منطقية. يحدث هذا بشكل ضمني عندما تصطدم معاملات مثل + و == و if بقيمة ليست من النوع المتوقع، ويحدث بشكل صريح عندما تستدعي أنت بنفسك Number(x) أو String(x) أو Boolean(x).
ما الفرق بين التحويل الضمني والتحويل الصريح؟
التحويل الصريح هو أن تستدعي دالة التحويل بشكل مقصود، مثل Number("42") و String(99) و Boolean(value). أما التحويل الضمني فيحدث من وراء ظهرك بسبب أحد المعاملات: مثلًا "5" - 2 تعطيك 3، بينما "5" + 2 تعطيك "52". التحويل الصريح واضح ويمكن التنبؤ به، أما الضمني فهو السبب الرئيسي لمعظم الأخطاء من نوع «لماذا ظهرت لي NaN فجأة؟».
كيف أحوّل نصًا إلى رقم في JavaScript؟
استخدم Number("42") للتحويل الصارم (يُرجع NaN إذا لم يكن النص رقمًا صالحًا)، أو استخدم parseInt("42px", 10) و parseFloat("3.14em") إذا كنت تريد استخراج الرقم من بداية نص فوضوي. كما أن معامل + الأحادي (مثل +"42") يعمل بنفس طريقة Number()، لكنه يسهل تفويته عند مراجعة الكود بسرعة.
لماذا تُرجع [] + [] نصًا فارغًا؟
[] + [] نصًا فارغًا؟لأن معامل + ليس له معنى رقمي مع مصفوفتين، فتلجأ JavaScript إلى تحويل الطرفين إلى نصوص. والمصفوفة تُحوَّل إلى نص عن طريق ربط عناصرها بفواصل، والمصفوفة الفارغة تُنتج "". وبذلك تصبح العملية "" + "" والنتيجة "". حيلة طريفة، لكنها سبب وجيه لكي لا تعتمد أبدًا على + مع القيم غير البدائية.