قيمة افتراضية أذكى
لطالما وفّرت جافا سكريبت طريقة مختصرة لتعيين القيم الافتراضية باستخدام value || fallback. هذه الطريقة تؤدي الغرض في أغلب الحالات، لكنها تحمل عيبًا جوهريًا: فهي تعامل أي قيمة falsy وكأنها قيمة مفقودة. فالأرقام مثل 0 والسلاسل الفارغة '' والقيمة false جميعها تُفعّل القيمة البديلة، حتى لو كانت هي الإجابة الصحيحة فعلًا.
وهنا يأتي دور معامل الدمج العدمي ?? (nullish coalescing) ليحل هذه المشكلة، إذ لا ينتقل إلى القيمة البديلة إلا عندما تكون القيمة null أو undefined تحديدًا:
0 و '' تعبر بسلام. أما null و undefined فلا. هذه خلاصة المعامل كله.
لماذا لا يكفي ||؟
إليك الخطأ الشهير الذي جاء المعامل ?? أصلاً لعلاجه:
طلب المستخدم مستوى صوت 0 (يعني كتم الصوت) واسمًا مستعارًا فارغًا. لكن القيمتين اتداسوا بهدوء لأن || ما يعرفش يفرّق بين "القيمة ناقصة" و"القيمة falsy".
خلّينا نستبدله بـ ??:
الآن القيمتان 0 و '' تمرّان كما هما دون تغيير، ولا تُستبدل بالقيمة الافتراضية إلا الحقول المفقودة فعلاً. وهذا هو السلوك المطلوب في الغالبية العظمى من الحالات.
الفكرة الذهنية وراء المعامل
المعامل ?? يطرح سؤالاً واحداً فقط: هل الطرف الأيسر null أو undefined؟ لا شيء غير ذلك.
| القيمة في الطرف الأيسر | ناتج left || right | ناتج left ?? right |
|---|---|---|
null | الطرف الأيمن | الطرف الأيمن |
undefined | الطرف الأيمن | الطرف الأيمن |
0 | الطرف الأيمن | الطرف الأيسر (0) |
'' | الطرف الأيمن | الطرف الأيسر ('') |
false | الطرف الأيمن | الطرف الأيسر (false) |
NaN | الطرف الأيمن | الطرف الأيسر (NaN) |
| أي كائن (object) | الطرف الأيسر | الطرف الأيسر |
استخدم ?? عندما تحتوي بياناتك على قيم falsy لها معنى حقيقي. أما || فاستخدمه حين تريد فعلاً رفض كل قيمة falsy (كالسلاسل الفارغة، أو العدّادات الصفرية، وما شابه) — هذه الحالة موجودة، لكنها أندر مما يظن كثيرون.
التقييم القصير (Short-Circuiting)
تماماً كما في || و &&، المعامل ?? يستخدم التقييم القصير. إذا لم يكن الطرف الأيسر nullish، فلن يُنفَّذ الطرف الأيمن على الإطلاق:
expensiveDefault() يُنفَّذ مرة واحدة فقط — من أجل b. وهذا مفيد عندما تكون القيمة الافتراضية عبارة عن استدعاء دالة أو طلب شبكة أو أي عملية تفضّل تجنّبها ما لم تكن ضرورية فعلاً.
الدمج مع optional chaining
جاء المعاملان ?? و ?. معاً في ES2020، وقد صُمِّما ليعملا جنباً إلى جنب. المعامل ?. يمرّ عبر مسار قد يكون أحد أجزائه مفقوداً ويُرجع undefined إذا غابت أي خطوة، ثم يأتي دور معامل الدمج العدمي ليضع قيمة افتراضية مناسبة:
بدون هذا الثنائي، يتحوّل الكود نفسه إلى سلسلة قبيحة من فحوصات && أو كتلة try/catch. أما معهما، فيصبح الوصول الآمن وضبط القيم الافتراضية المعقولة ممكنًا في سطر واحد.
معامل الإسناد العدمي: ??=
العبارة a ??= b تُسنِد قيمة b إلى a فقط إذا كانت a تساوي null أو undefined. هذا هو معامل الإسناد المنطقي العدمي (nullish coalescing assignment)، وهو يعمل بمنطق التقييم القصير (short-circuit) — أي إذا كانت a تحمل قيمة بالفعل، فلن يُقيَّم الطرف الآخر من الإسناد أصلًا.
لاحظ أن verbose: false و retries: 0 بقيتا كما هما — فالمعامل ??= لا يملأ إلا ما هو مفقود فعلاً. قارن ذلك بالمعامل ||= الذي كان سيدوس على القيمتين معاً.
الأولوية وقاعدة الأقواس
صُمِّم المعامل ?? بحيث يرفض عمداً الاختلاط مع || أو && دون أقواس. الكود التالي يرمي خطأ SyntaxError:
const x = a || b ?? c; // خطأ في بناء الجملة
مصممو اللغة شافوا إن أولوية العمليات هنا مش واضحة بالشكل الكافي، فقرّروا يجبروك تحدّدها صراحةً. ضيف الأقواس ويمشي الحال:
تجميعات مختلفة تعطي نتائج مختلفة. الأقواس هنا ليست للزينة، بل تُوضّح لمن يقرأ الكود لاحقًا (وللمحلل اللغوي أيضًا) ما الذي كنت تقصده فعلًا.
ليس نفس القيمة الافتراضية للمعامِل
للقيم الافتراضية في بارامترات الدوال قاعدة خاصة بها: تعمل فقط عندما تكون القيمة المُمرَّرة undefined، أما null فلا. وهذا فرق دقيق عن المعامل ?? الذي يتعامل مع الحالتين بنفس الطريقة.
إذا أردت أن تُفعِّل القيمة null بدورها القيمة الافتراضية، استخدم ?? داخل الدالة:
فرق بسيط، لكنه يوقعك بسهولة حين يُرجِع لك أحد الـ APIs قيمة null للتعبير عن "لا توجد قيمة محددة".
متى تستخدم المعامل ?? في جافا سكريبت؟
اجعل ?? خيارك الافتراضي ما لم يكن لديك سبب واضح لاستخدام غيره. فهو المعامل الذي ينسجم مع طبيعة البيانات في معظم الحالات — القيم مثل 0 و '' و false غالبًا ما تكون قيمًا حقيقية تستحق الإبقاء عليها، والبيانات المفقودة فعلًا هي وحدها التي تستحق قيمة افتراضية بديلة. أمّا || فاحتفظ به للحالات التي تريد فيها استبدال أي قيمة falsy دون استثناء.
التالي: الأصناف (Classes)
بهذا نكون قد أنهينا الكائنات والمصفوفات — أصبحت بين يديك الأدوات اللازمة لبناء هياكل البيانات والتنقل بينها بأمان. في الفصل القادم ننتقل إلى تنظيم السلوك: الأصناف، والوراثة، ونظام الـ prototype الذي يقف خلفها.
الأسئلة الشائعة
ما هو معامل الدمج العدمي (Nullish Coalescing) في JavaScript؟
المعامل ?? يُرجع الطرف الأيمن فقط إذا كان الطرف الأيسر null أو undefined، وفي ما عدا ذلك يُرجع قيمة الطرف الأيسر كما هي. الصيغة value ?? fallback هي الطريقة المثلى لتحديد قيمة افتراضية دون الوقوع في فخ القيم الصالحة التي تُعتبر falsy مثل 0 أو ''.
ما الفرق بين ?? و || في JavaScript؟
المعامل || يتحوّل إلى القيمة البديلة مع أي قيمة falsy مثل 0 و '' و false و NaN و null و undefined. أما ?? فيتحوّل فقط مع null و undefined. فإذا كان الرقم 0 أو النص الفارغ قيمة صالحة في بياناتك، استخدم ??. أما إذا كنت تقصد فعلاً رفض كل القيم الـ falsy، فـ || لا يزال الخيار الصحيح.
هل يمكن دمج معامل ?? مع Optional Chaining؟
نعم، وهذا من أشهر الاستخدامات معاً. السطر user?.settings?.theme ?? 'light' يتنقّل عبر الخصائص بأمان، ويُرجع undefined إذا كانت أي حلقة في السلسلة مفقودة، ثم يستبدلها بـ 'light'. المعاملان أُطلقا معاً في ES2020 تحديداً لدعم هذا النمط.
ماذا يفعل المعامل ??= ؟
التعبير a ??= b يُسند قيمة b إلى a فقط إذا كانت a حالياً null أو undefined. هو اختصار لـ a = a ?? b، لكنه يعمل بأسلوب short-circuit: إذا كانت a تحمل قيمة فعلاً، لن يُقيَّم الطرف الأيمن أصلاً. مفيد جداً لتعبئة القيم الناقصة في كائن options.