أقصر سؤال يفصل مبتدئ جافاسكريبت عن متقنها، وأحد أثبت أسئلة المقابلات: ما الفرق بين == و===؟ الجواب المختصر سهل، لكن الفهم الحقيقي يحميك من خطأين: خطأ من يستعمل ==، وخطأ من يستعمل === دون أن يعرف نوع قيمته.
كل النتائج في هذا المقال نُفّذت فعليًا على Node.js 22، وهي نفسها في كل المتصفحات لأنها جزء من مواصفة اللغة.
الفكرة في سطرين
===(المساواة الصارمة): إذا اختلف النوعان فالنتيجةfalseفورًا. إن تطابقا قارنت القيمة.==(المساواة المرنة): إذا اختلف النوعان حوّلت أحدهما أو كليهما، ثم قارنت.
"5" === 5 // false — نص ورقم، نوعان مختلفان
"5" == 5 // true — النص "5" تحوّل للرقم 5 أولًا
كيف تحوّل == الأنواع فعلًا
لا تحتاج حفظ المواصفة كاملة. هذه القواعد الأربع تشرح كل الحالات الغريبة الشهيرة:
nullوundefinedيساويان بعضهما فقط، ولا يساويان أي قيمة أخرى. لا تحويل هنا إطلاقًا.- نص مقابل رقم: النص يتحوّل لرقم (
Number("5")=5، وNumber("")=0). - قيمة منطقية (boolean) مقابل أي شيء: الـ boolean يتحوّل لرقم أولًا (
true→1،false→0)، ثم تُطبَّق القواعد من جديد. - كائن أو مصفوفة مقابل قيمة بسيطة: الكائن يتحوّل لقيمة بسيطة، والمصفوفة تصبح نصًا (
[1,2]→"1,2"، و[]→"").
والآن طبّقها على "معرض العجائب":
0 == "" // true — قاعدة 2: "" → 0
0 == "0" // true — قاعدة 2: "0" → 0
"" == "0" // false — نصان من نفس النوع، فلا تحويل، وهما مختلفان
false == "0" // true — قاعدة 3: false → 0، ثم قاعدة 2: "0" → 0
" \t\n" == 0 // true — نص من مسافات فقط يتحوّل لـ 0
null == 0 // false — قاعدة 1: null لا يساوي إلا undefined
[] == false // true — قاعدة 3: false → 0، ثم قاعدة 4: [] → "" ، ثم "" → 0
[1] == 1 // true — [1] → "1" → 1
[] == ![] // true — ![] هي false لأن أي مصفوفة "truthy"، ثم كما في السطر السابق
لاحظ 0 == "" و0 == "0" صحيحتان لكن "" == "0" خاطئة. المساواة المرنة ليست متعدية: إذا كان أ = ب وب = ج فلا يلزم أن أ = ج. هذا وحده سبب كافٍ لتجنّبها في الشروط.
ومع === كل الأسطر السابقة false، ببساطة لأن الأنواع مختلفة.
الفخ المعاكس: === مع قيم هي نصوص دائمًا
بعد أن تعتاد === ستقع في هذا الخطأ مرة واحدة على الأقل:
// الرابط: /products?page=1
const page = new URLSearchParams(location.search).get("page");
if (page === 1) {
// لن يُنفَّذ أبدًا: page هي النص "1" وليست الرقم 1
}
كل ما يأتي من الروابط (URLSearchParams)، وحقول النماذج (input.value)، وlocalStorage، ومتغيّرات البيئة (process.env) نصوص دائمًا، حتى لو كتبت فيها رقمًا. == كانت "ستنجح" هنا بالصدفة، والحل ليس العودة لها، بل تحويل النوع صراحة مرة واحدة عند المصدر:
const page = Number(new URLSearchParams(location.search).get("page"));
if (page === 1) {
// يعمل
}
وانتبه لما يرجعه Number() في الحالات الحدّية:
Number("12") // 12
Number("") // 0 ← حقل فارغ يصبح صفرًا، تحقّق منه قبل التحويل
Number("12px") // NaN
parseInt("12px", 10) // 12 — يقرأ الأرقام من البداية ويتوقف عند أول حرف
القاعدة: === تقول لك الحقيقة عن الأنواع؛ إن فشلت مقارنة "يجب أن تنجح" فالمشكلة غالبًا أن القيمة ليست من النوع الذي تظنه. اطبع typeof page قبل أن تغيّر عملية المقارنة.
أين تستخدم اللغة === بالنيابة عنك
switchيقارن بـ===:
const x = "1";
switch (x) {
case 1:
console.log("لن يصل هنا");
break;
default:
console.log("default"); // هذا ما يُطبع
}
indexOfوlastIndexOfتستخدم===، أماincludesوMapوSetفتستخدم مقارنة اسمها SameValueZero، تشبه===لكنها تعتبرNaNمساوية لنفسها:
[NaN].indexOf(NaN) // -1
[NaN].includes(NaN) // true
new Map([[NaN, 1]]).get(NaN) // 1
عمليات الترتيب ليس لها نسخة صارمة
< و> و<= و>= تحوّل الأنواع دائمًا، ولا يوجد <==:
"10" < "9" // true — نصّان: مقارنة حرف بحرف، و"1" قبل "9"
"10" < 9 // false — نص ورقم: "10" → 10
null >= 0 // true — مع أن null == 0 خاطئة!
السطر الأخير مثال ممتاز على أن == و>= تتبعان قواعد مختلفة. إن كنت ترتّب أو تقارن قيمًا جاءت كنصوص (أسعار من جدول، أعمار من نموذج)، حوّلها لأرقام أولًا.
الاستثناء المقبول الوحيد: x == null
بحسب القاعدة 1، x == null صحيحة فقط عندما تكون x هي null أو undefined، وليس 0 ولا "" ولا false:
if (x == null) { /* ... */ }
// تعادل تمامًا:
if (x === null || x === undefined) { /* ... */ }
هذا الاستخدام الوحيد لـ == الذي تقبله أغلب أدلة الأسلوب، وحتى هو صار أقل حاجة مع ?? و?.. الفرق بين القيمتين بالتفصيل في الفرق بين undefined و null.
حدود === نفسها
الكائنات تُقارن بالمرجع
({ a: 1 }) === ({ a: 1 }) // false — كائنان مختلفان في الذاكرة
[1, 2] === [1, 2] // false
const a = { x: 1 };
const b = a;
a === b // true — نفس الكائن
هذا سلوك العمليتين معًا وليس عيبًا في إحداهما، وهو نفسه سبب حلقة useEffect اللانهائية في React حين تضع كائنًا جديدًا في مصفوفة الاعتماديات.
NaN والصفر السالب، وObject.is
NaN === NaN // false — القيمة الوحيدة التي لا تساوي نفسها
Number.isNaN(x) // الفحص الصحيح
Object.is(NaN, NaN) // true
0 === -0 // true
Object.is(0, -0) // false
Object.is مساواة ثالثة أدق في هاتين الحالتين فقط. اعرفها للمقابلات، واستعمل === في عملك اليومي.
اجعل الأداة تتذكّر عنك
قاعدة ESLint eqeqeq تمنع == و!=، ومع خيار null: "ignore" تسمح بالاستثناء الوحيد:
{
"rules": {
"eqeqeq": ["error", "always", { "null": "ignore" }]
}
}
قائمة تحقّق
- اكتب
===و!==دائمًا. - القيم من الروابط والنماذج وlocalStorage وprocess.env نصوص: حوّلها بـ
Number()عند المصدر. - مقارنة "يجب أن تنجح" فشلت؟ اطبع
typeofقبل أي شيء. <و>تحوّل دائمًا: حوّل لأرقام قبل الترتيب.x == nullهو الاستخدام المقبول الوحيد لـ==.- الكائنات تُقارن بالمرجع، و
NaNتُفحص بـNumber.isNaN.
رسّخ الأساس في مسار جافاسكريبت بالعربي، وأكمل مع الفرق بين var و let و const. وإن أردت أن تُكتشف أخطاء الأنواع قبل التشغيل أصلًا، فاقرأ الفرق بين JavaScript و TypeScript.