تخطَّ إلى المحتوى

١٢ يوليو ٢٠٢٦ · آخر تحديث: ١٩ أكتوبر ٢٠٢٦

الفرق بين == و === في جافاسكريبت

شارك المقال:

أقصر سؤال يفصل مبتدئ جافاسكريبت عن متقنها، وأحد أثبت أسئلة المقابلات: ما الفرق بين == و===؟ الجواب المختصر سهل، لكن الفهم الحقيقي يحميك من خطأين: خطأ من يستعمل ==، وخطأ من يستعمل === دون أن يعرف نوع قيمته.

كل النتائج في هذا المقال نُفّذت فعليًا على Node.js 22، وهي نفسها في كل المتصفحات لأنها جزء من مواصفة اللغة.

الفكرة في سطرين

  • === (المساواة الصارمة): إذا اختلف النوعان فالنتيجة false فورًا. إن تطابقا قارنت القيمة.
  • == (المساواة المرنة): إذا اختلف النوعان حوّلت أحدهما أو كليهما، ثم قارنت.
"5" === 5   // false — نص ورقم، نوعان مختلفان
"5" == 5    // true  — النص "5" تحوّل للرقم 5 أولًا

كيف تحوّل == الأنواع فعلًا

لا تحتاج حفظ المواصفة كاملة. هذه القواعد الأربع تشرح كل الحالات الغريبة الشهيرة:

  1. null وundefined يساويان بعضهما فقط، ولا يساويان أي قيمة أخرى. لا تحويل هنا إطلاقًا.
  2. نص مقابل رقم: النص يتحوّل لرقم (Number("5") = 5، وNumber("") = 0).
  3. قيمة منطقية (boolean) مقابل أي شيء: الـ boolean يتحوّل لرقم أولًا (true → 1، false → 0)، ثم تُطبَّق القواعد من جديد.
  4. كائن أو مصفوفة مقابل قيمة بسيطة: الكائن يتحوّل لقيمة بسيطة، والمصفوفة تصبح نصًا ([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.

📚 مصادر رسمية للتعمّق: توثيق JavaScript على MDN

الأسئلة الشائعة

لماذا وُجدت == أصلًا إذا كانت مصدر مشاكل؟

جافاسكريبت صُممت عام 1995 لتكون متسامحة مع صفحات ويب بسيطة، فكان التحويل التلقائي بين النصوص والأرقام يبدو تسهيلًا. لاحقًا اتضح أن هذا التسامح يخفي الأخطاء بدل أن يمنعها، فأضيفت === الصارمة، وبقيت == حتى لا ينكسر الكود القديم.

هل NaN === NaN صحيحة؟

لا. NaN هي القيمة الوحيدة التي لا تساوي نفسها بـ == ولا بـ ===. افحصها بـ Number.isNaN(x)، أو Object.is(x, NaN). ولهذا [NaN].indexOf(NaN) يرجع -1 لأنها تستخدم ===، بينما [NaN].includes(NaN) يرجع true لأنها تستخدم مقارنة مختلفة تعتبر NaN مساوية لنفسها.

لماذا تفشل المقارنة بين كائنين متطابقين حتى بـ ===؟

لأن الكائنات والمصفوفات تُقارن بالمرجع لا بالمحتوى: ({a:1}) === ({a:1}) خاطئة لأنهما كائنان مختلفان في الذاكرة. للمقارنة بالمحتوى قارن الحقول بنفسك، أو استخدم JSON.stringify للحالات البسيطة التي يكون فيها ترتيب المفاتيح ثابتًا.

هل يوجد إصدار صارم من < و >؟

لا. عمليات الترتيب < و > و <= و >= تحوّل الأنواع دائمًا، ولا يوجد <== مثلًا. لذلك '10' < '9' صحيحة (مقارنة نصوص حرفًا بحرف) بينما '10' < 9 خاطئة (تحويل لأرقام). الحل أن تحوّل القيم لأرقام بنفسك قبل المقارنة.

اقرأ أيضًا

تصفّح كل المقالات في المدوّنة، أو ابدأ التعلّم من المسارات و خرائط الطريق.