المستوى: مبتدئ. لو بتكتب if (x == 0) وفاكر إنها بتقارن الأرقام وبس، اللي جاي هنا هيوريك ليه السطر ده ممكن يكسر تطبيقك في حالات ما تتوقعهاش.
الفرق بين == و === في جافاسكريبت
الخلاصة قبل أي شرح: استخدم === كإعداد افتراضي، وسيب == لحالات نادرة تعرفها بالظبط. السبب إن == بيحوّل الأنواع من ورا ظهرك قبل ما يقارن، وده بيطلّع نتائج غريبة زي '' == 0 اللي بترجع true.
المشكلة باختصار
في جافاسكريبت فيه طريقتين للمقارنة: == (المرن) و=== (الصارم). الاتنين شكلهم متقارب، بس بيتصرفوا بشكل مختلف تمامًا. اللي بيحصل فعلاً إن == بيحاول "يوفّق" بين النوعين المختلفين قبل المقارنة، فبيطلّع نتائج بتضحك من غير ما تضحك. النتيجة: باجات صامتة صعب تلاقيها.
الأول نبدأ بمثال بسيط
تخيّل عندك بوابين على مدخل مبنى. البواب الأول (وده ==) بيعرف الناس "بالتقريب": لو حد قاله "أنا رقم صفر"، وحد تاني جه من غير ما يقول حاجة (نص فاضي)، البواب يقول "معلش، الاتنين قريبين من بعض، دخّلهم على إنهم نفس الشخص". النتيجة إنه بيتخدع.
البواب التاني (وده ===) بيطلب البطاقة. بيبص على النوع والقيمة مع بعض: لازم يكونوا "رقم" و"رقم"، وبنفس القيمة. لو واحد "نص" وواحد "رقم"، يرفض على طول. البواب ده مبيتخدعش، وده اللي انت عايزه في 95% من الحالات.
دلوقتي المفهوم العلمي: التحويل الضمني
لما تكتب == بين نوعين مختلفين، جافاسكريبت بتعمل حاجة اسمها التحويل الضمني للنوع (Type Coercion): بتحوّل أحد الطرفين عشان يبقى نفس نوع الطرف التاني، وبعدين تقارن. في حالة نص مقابل رقم، بتحوّل النص لرقم. فـ '' == 0 بتتحوّل لـ Number('') == 0، والنص الفاضي بيتحوّل لـ 0، فتبقى 0 == 0 وترجع true.
أما === فبيقارن من غير أي تحويل. لو النوعين مختلفين، النتيجة false على طول، خلاص. ده اللي خلّاه أوضح وأأمن.
// == بيحوّل الأنواع قبل المقارنة (مفاجآت)
console.log('' == 0); // true → Number('') يساوي 0
console.log('0' == false); // true → الاتنين بيتحوّلوا لـ 0
console.log([] == ![]); // true → [] بيتحوّل لـ '' ثم لـ 0
console.log(null == undefined); // true
// === بيقارن النوع والقيمة معًا (متوقّع)
console.log('' === 0); // false
console.log('0' === false); // false
console.log(0 === 0); // true
مثال من الواقع بيكسر تطبيقك
افترض عندك متجر، وبتتحقق من كمية المنتج اللي المستخدم كتبها في حقل الإدخال:
// المستخدم مسح الحقل، فـ qty بقى نص فاضي ''
const qty = inputElement.value; // ''
if (qty == 0) {
showOutOfStock(); // بيتنفّذ بالغلط! لأن '' == 0 = true
}
المستخدم ساب الحقل فاضي، مقصدش يطلب صفر، بس '' == 0 رجعت true، فطلعتله رسالة "نفد المخزون" غلط. لو عندك تطبيق بـ 50 ألف طلب في اليوم، و2% بس من الناس بيسيبوا الحقل فاضي، ده حوالي 1000 حالة غلط في اليوم من سطر واحد. الحل:
const qty = Number(inputElement.value); // حوّل بشكل صريح
if (qty === 0) {
showOutOfStock(); // دلوقتي بيشتغل صح؛ '' بقى NaN مش 0
}
الـ trade-off ومتى == مقبول
الـ trade-off هنا بسيط: === بيدّيك وضوح وأمان، مقابل إنك أحيانًا بتضطر تحوّل الأنواع بنفسك بسطر زيادة (زي Number(...)). ده مكسب كبير مقابل تكلفة تافهة.
فيه حالة واحدة شائعة الـ == بيبقى مقبول فيها: لما تعوز تتحقق من null وundefined مع بعض في مقارنة واحدة، لأن value == null بترجع true لو القيمة null أو undefined. الافتراض إن الفريق كله فاهم النية دي، وإلا استخدم الصريح.
متى لا تستخدم هذه الطريقة
متعتمدش على == عشان "توفّر" تحويل نوع. ولو بتكتب TypeScript أو عندك ESLint بقاعدة eqeqeq، الأداة أصلًا هتمنعك من ==، فمتحاولش تلفّ حواليها. وكمان === مش بيحل مشاكل مقارنة الكائنات: {} === {} بترجع false لأنهم مرجعين مختلفين في الذاكرة، ده موضوع تاني خالص.
الخطوة التالية
افتح أقرب ملف جافاسكريبت عندك ودوّر على == و!=. لو مش متأكد من نية أي واحدة منهم، حوّلها لـ === و!==، وحوّل الأنواع بشكل صريح بـ Number() أو String() قبل المقارنة. بعدها فعّل قاعدة eqeqeq في ESLint عشان تمسكها تلقائيًا في المستقبل.
المصادر
- MDN — Equality comparisons and sameness: developer.mozilla.org
- MDN — Loose equality (==): developer.mozilla.org
- ECMAScript Specification — IsLooselyEqual (Abstract Equality): tc39.es/ecma262
- ESLint — قاعدة eqeqeq: eslint.org