الفصل 01
القيم والأنواع والمعاملات
تحت سطح الآلة، يتحرك البرنامج. بلا جهد، يتوسع وينكمش. في انسجام عظيم، تتناثر الإلكترونات وتتجمع من جديد. ليست الأشكال على الشاشة سوى تموجات على الماء. أما الجوهر فيبقى في الأسفل، غير مرئي.
— المعلم يوان-ما، كتاب البرمجة

في عالم الحاسوب، لا يوجد سوى البيانات. يمكنك قراءة البيانات وتعديلها وإنشاء بيانات جديدة — لكن ما ليس بيانات لا يمكن ذكره. تُخزَّن كل هذه البيانات على شكل سلاسل طويلة من البتات، ومن ثم فهي متشابهة في جوهرها.
البتات (bits) هي أي نوع من الأشياء ذات القيمتين، وتوصف عادةً بالصفر والواحد. داخل الحاسوب، تتخذ أشكالاً مثل شحنة كهربائية عالية أو منخفضة، أو إشارة قوية أو ضعيفة، أو نقطة لامعة أو باهتة على سطح قرص مضغوط. يمكن اختزال أي قطعة من المعلومات المتقطعة إلى سلسلة من الأصفار والواحدات، ومن ثم تمثيلها بالبتات.
على سبيل المثال، يمكننا التعبير عن العدد 13 بالبتات. يجري هذا بالطريقة نفسها التي يجري بها الأمر مع عدد عشري، لكن بدلاً من 10 أرقام مختلفة، لدينا 2 فقط، ويزداد وزن كل رقم بمعامل 2 من اليمين إلى اليسار. إليك البتات التي يتكوّن منها العدد 13، مع أوزان الأرقام موضحة أسفلها:
0 0 0 0 1 1 0 1
128 64 32 16 8 4 2 1
هذا هو العدد الثنائي 00001101. تمثل أرقامه غير الصفرية 8 و4 و1، ومجموعها 13.
القيم
تخيّل بحراً من البتات — محيطاً منها. يحتوي حاسوب حديث نموذجي على أكثر من 100 مليار بت في ذاكرته المتطايرة (ذاكرة العمل). أما التخزين غير المتطاير (القرص الصلب أو ما يعادله) فيميل إلى احتواء عدد أكبر من ذلك ببضع مراتب قدرية.
لتتمكن من العمل مع هذه الكميات من البتات دون أن تضيع، نفصلها إلى كتل تمثل قطعاً من المعلومات. في بيئة JavaScript، تُسمى تلك الكتل قيم (values). ورغم أن كل القيم مصنوعة من بتات، فإنها تؤدي أدواراً مختلفة. ولكل قيمة نوع يحدد دورها. بعض القيم أعداد، وبعضها قطع من النص، وبعضها دوال، وهكذا.
لإنشاء قيمة، ما عليك إلا أن تستدعي اسمها. هذا مريح. لا يتعين عليك جمع مواد البناء لقيمك أو الدفع مقابلها. تكتفي بطلب واحدة، ووفجأة، تكون لديك. بالطبع، لا تُخلق القيم حقاً من العدم. فلا بد من تخزين كل واحدة في مكان ما، وإذا أردت استخدام عدد هائل منها في الوقت نفسه، فقد تنفد ذاكرة الحاسوب. لحسن الحظ، هذه مشكلة فقط إذا كنت بحاجة إليها كلها في آن واحد. وبمجرد ألا تستخدم قيمة بعد الآن، فإنها تتلاشى، تاركةً بتاتها لإعادة تدويرها كمواد بناء للجيل التالي من القيم.
يقدّم ما تبقى من هذا الفصل العناصر الذرية لبرامج JavaScript، أي أنواع القيم البسيطة والمعاملات التي يمكنها التأثير في مثل هذه القيم.
الأعداد
قيم النوع number هي، كما هو متوقع، قيم عددية. في برنامج JavaScript، تُكتب على النحو التالي:
13
استخدام ذلك في برنامج يؤدي إلى ظهور نمط البتات الخاص بالعدد 13 داخل ذاكرة الحاسوب.
تستخدم JavaScript عدداً ثابتاً من البتات، هو 64 بت، لتخزين قيمة عددية واحدة. وعدد الأنماط التي يمكنك إنشاؤها بـ64 بت محدود، ما يحد من عدد الأعداد المختلفة التي يمكن تمثيلها. بـN من الأرقام العشرية، يمكنك تمثيل 10N عدداً. وبالمثل، إذا أُتيح لك 64 رقماً ثنائياً، يمكنك تمثيل 264 عدداً مختلفاً، وهو نحو 18 كوينتليون (18 متبوعاً بـ18 صفراً). هذا كثير.
كانت ذاكرة الحاسوب أصغر بكثير في الماضي، وكان الناس يميلون إلى استخدام مجموعات من 8 أو 16 بت لتمثيل أعدادهم. وكان من السهل أن تفيض (overflow) مثل هذه الأعداد الصغيرة عن الحد بطريق الخطأ — أي أن ينتهي بك الأمر إلى عدد لا يتسع في العدد المحدد من البتات. اليوم، حتى الحواسيب التي تتسع في جيبك لديها ذاكرة وفيرة، لذا لك مطلق الحرية في استخدام كتل من 64 بت، ولا تحتاج إلى القلق بشأن الفيض إلا عند التعامل مع أعداد فلكية حقاً.
لكن ليست كل الأعداد الكاملة الأقل من 18 كوينتليون تتسع في عدد JavaScript. فتلك البتات تخزّن أيضاً الأعداد السالبة، لذا تشير بتة واحدة إلى إشارة العدد. والمسألة الأكبر هي تمثيل الأعداد غير الكاملة. للقيام بذلك، تُستخدم بعض البتات لتخزين موضع الفاصلة العشرية. والحد الأقصى الفعلي للعدد الكامل الذي يمكن تخزينه يقع أقرب إلى نطاق 9 كوادريليون (15 صفراً) — وهو ما يزال كبيراً بشكل مبهج.
تُكتب الأعداد الكسرية باستخدام نقطة:
9.81
بالنسبة إلى الأعداد الكبيرة جداً أو الصغيرة جداً، يمكنك أيضاً استخدام الترميز العلمي بإضافة e (اختصاراً لـexponent، أي الأس)، متبوعة بأس العدد.
2.998e8
هذا يساوي 2.998 × 108 = 299,800,000.
الحسابات بالأعداد الكاملة (وتسمى أيضاً الأعداد الصحيحة — integers) الأصغر من 9 كوادريليون المذكور آنفاً مضمونة الدقة دائماً. لسوء الحظ، الحسابات بالأعداد الكسرية ليست كذلك عموماً. وكما لا يمكن التعبير عن π (باي) بدقة بعدد منتهٍ من الأرقام العشرية، تفقد أعداد كثيرة بعض دقتها عندما تتاح 64 بت فقط لتخزينها. هذا مؤسف، لكنه يسبب مشكلات عملية في حالات محددة فقط. المهم أن تدرك ذلك وأن تتعامل مع الأعداد الرقمية الكسرية كتقريبات، لا كقيم دقيقة.
الحساب
الشيء الرئيسي الذي تفعله بالأعداد هو الحساب. تأخذ العمليات الحسابية مثل الجمع أو الضرب قيمتين عدديتين وتنتج عدداً جديداً منهما. وإليك شكلها في JavaScript:
100 + 4 * 11
يسمى الرمزان + و* معاملين (operators). الأول يعني الجمع والثاني يعني الضرب. ووضع معامل بين قيمتين يطبقه على تلك القيمتين وينتج قيمة جديدة.
هل يعني هذا المثال «اجمع 4 و100، ثم اضرب الناتج في 11»، أم أن الضرب يجري قبل الجمع؟ كما ربما خمّنت، يحدث الضرب أولاً. وكما في الرياضيات، يمكنك تغيير ذلك بوضع الجمع بين قوسين.
(100 + 4) * 11
للطرح يوجد المعامل -. ويمكن إجراء القسمة بالمعامل /.
عندما تظهر معاملات معاً دون أقواس، يحدد ترتيب تطبيقها أسبقية (precedence) المعاملات. ويوضح المثال أن الضرب يأتي قبل الجمع. وللمعامل / الأسبقية نفسها التي للمعامل *. وبالمثل، للأمرين + و- الأسبقية نفسها. وعندما تظهر عدة معاملات بالأسبقية نفسها جنباً إلى جنب، كما في 1 - 2 + 1، تُطبَّق من اليسار إلى اليمين: (1 - 2) + 1.
لا تقلق كثيراً بشأن قواعد الأسبقية هذه. عند الشك، أضف أقواساً فحسب.
ثمة معامل حسابي آخر، قد لا تتعرف عليه فوراً. يُستخدم الرمز % لتمثيل عملية الباقي (remainder). X % Y هو باقي قسمة X على Y. على سبيل المثال، ينتج 314 % 100 القيمة 14، ويعطي 144 % 12 القيمة 0. وأسبقية معامل الباقي هي نفسها أسبقية الضرب والقسمة. وسترى أيضاً هذا المعامل كثيراً ما يُشار إليه باسم مودولو (modulo).
أعداد خاصة
ثمة ثلاث قيم خاصة في JavaScript تُعدّ أعداداً لكنها لا تتصرف مثل الأعداد العادية. الأولى اثنتان هما Infinity و-Infinity، وتمثلان اللانهاية الموجبة والسالبة. Infinity - 1 تبقى Infinity، وهكذا. لكن لا تثق كثيراً في الحسابات القائمة على اللانهاية. فهي غير سليمة رياضياً، وستقودك سريعاً إلى العدد الخاص التالي: NaN.
يرمز NaN إلى «ليس عدداً» (not a number)، رغم أنه قيمة من النوع number. وستحصل على هذه النتيجة عندما تحاول، مثلاً، حساب 0 / 0 (صفر مقسوماً على صفر)، أو Infinity - Infinity، أو أي عدد آخر من العمليات العددية التي لا تسفر عن نتيجة ذات معنى.
النصوص
النوع الأساسي التالي من البيانات هو النص (string). تُستخدم النصوص لتمثيل النص المكتوب. وتُكتب بإحاطة محتواها بعلامات اقتباس.
`Down on the sea`
"Lie on the ocean"
'Float on the ocean'
يمكنك استخدام علامات اقتباس مفردة أو مزدوجة أو علامات backtick لوسم النصوص، شرط أن تتطابق علامتا الاقتباس في بداية النص ونهايته.
يمكنك وضع أي شيء تقريباً بين علامات الاقتباس لتصنع منه JavaScript قيمة نصية. لكن قلة من المحارف أصعب من غيرها. يمكنك أن تتخيل كيف قد يكون وضع علامات اقتباس داخل علامات اقتباس أمراً صعباً، لأنها ستبدو وكأنها نهاية النص. ولا يمكن تضمين الأسطر الجديدة (newlines) — وهي المحارف التي تحصل عليها عند ضغط Enter — إلا عندما يكون النص محاطاً بعلامات backtick (```).
لجعل تضمين مثل هذه المحارف في نص ممكن، يُستخدم الترميز التالي: تشير الشرطة المائلة العكسية (\) داخل نص محاط بعلامات اقتباس إلى أن للمحرف الذي يليها معنى خاصاً. ويسمى هذا تهريب (escaping) المحرف. وعلامة اقتباس تسبقها شرطة مائلة عكسية لن تنهي النص بل ستكون جزءاً منه. وعندما يأتي المحرف n بعد شرطة مائلة عكسية، يُفسَّر على أنه سطر جديد. وبالمثل، يعني t بعد شرطة مائلة عكسية محرف tab. خذ النص التالي:
"This is the first line\nAnd this is the second"
هذا هو النص الفعلي في ذلك النص:
This is the first line
And this is the second
ثمة، بالطبع، حالات تريد فيها أن تكون الشرطة المائلة العكسية في نص مجرد شرطة مائلة عكسية، لا رمزاً خاصاً. وإذا تتابعت شرطتان مائلتان عكسيتان، فإنهما تنطويان معاً، ولا تبقى سوى واحدة في القيمة النصية الناتجة. وهكذا يمكن التعبير عن النص «يُكتب محرف السطر الجديد على الصورة "\n".»:
"A newline character is written like \"\\n\"."
النصوص أيضاً يجب نمذجتها كسلسلة من البتات لتوجد داخل الحاسوب. والطريقة التي تفعل بها JavaScript ذلك مبنية على معيار Unicode. يخصص هذا المعيار رقماً لكل محرف تقريباً قد تحتاج إليه يوماً، بما في ذلك محارف من اليونانية والعربية واليابانية والأرمينية وغيرها. وإذا كان لدينا رقم لكل محرف، يمكن وصف النص بسلسلة من الأرقام. وهذا ما تفعله JavaScript.
لكن ثمة تعقيد: يستخدم تمثيل JavaScript 16 بت لكل عنصر نصي، ما يمكنه وصف ما يصل إلى 216 محرفاً مختلفاً. غير أن Unicode يعرّف محارف أكثر من ذلك — نحو ضعف هذا العدد في الوقت الحالي. لذا تشغل بعض المحارف، مثل كثير من الرموز التعبيرية (emoji)، «موضعي محرف» في نصوص JavaScript. وسنعود إلى هذا في الفصل 5.
لا يمكن قسمة النصوص أو ضربها أو طرحها. لكن يمكن استخدام المعامل + عليها، لا للجمع بل للدمج (concatenate) — أي لصق نصين معاً. وسينتج السطر التالي النص "concatenate":
"con" + "cat" + "e" + "nate"
للقيم النصية عدد من الدوال المرتبطة بها (طرق — methods) يمكن استخدامها لإجراء عمليات أخرى عليها. سأقول المزيد عنها في الفصل 4.
النصوص المكتوبة بعلامات اقتباس مفردة أو مزدوجة تتصرف بشكل متشابه جداً — والفرق الوحيد يكمن في نوع علامة الاقتباس التي تحتاج إلى تهريبها بداخلها. أما النصوص المحاطة بعلامات backtick، وتسمى عادةً قوالب نصية (template literals)، فيمكنها القيام بحيل إضافية قليلة. فإلى جانب قدرتها على الامتداد عبر عدة أسطر، يمكنها أيضاً تضمين قيم أخرى.
`half of 100 is ${100 / 2}`
عندما تكتب شيئاً داخل ${} في قالب نصي، تُحسب نتيجته وتُحوَّل إلى نص ويُضمَّن في ذلك الموضع. وينتج هذا المثال النص "half of 100 is 50".
المعاملات الأحادية
ليست كل المعاملات رموزاً. بعضها يُكتب كلمات. ومن الأمثلة على ذلك المعامل typeof، الذي ينتج قيمة نصية تسمّي نوع القيمة التي تعطيها له.
console.log(typeof 4.5)
// → number
console.log(typeof "x")
// → string
سنستخدم console.log في الشيفرة المثالية للإشارة إلى أننا نريد رؤية نتيجة تقييم شيء ما. (المزيد عن ذلك في الفصل التالي.)
كل المعاملات الأخرى المعروضة حتى الآن في هذا الفصل عملت على قيمتين، لكن typeof يأخذ قيمة واحدة فقط. تسمى المعاملات التي تستخدم قيمتين معاملات ثنائية (binary)، أما التي تأخذ قيمة واحدة فتسمى معاملات أحادية (unary). ويمكن استخدام معامل الطرح (-) كمعامل ثنائي وكمعامل أحادي معاً.
console.log(- (10 - 2))
// → -8
القيم المنطقية
غالباً ما يكون من المفيد وجود قيمة تميّز بين احتمالين فقط، مثل «نعم» و«لا» أو «تشغيل» و«إيقاف». ولهذا الغرض، تمتلك JavaScript نوع Boolean، وله قيمتان فقط: true وfalse، تُكتبان بهاتين الكلمتين.
المقارنة
إليك طريقة لإنتاج قيم منطقية:
console.log(3 > 2)
// → true
console.log(3 < 2)
// → false
العلامتان > و< هما الرمزان التقليديان لـ«أكبر من» و«أصغر من» على الترتيب. وهما معاملان ثنائيان. ويؤدي تطبيقهما إلى قيمة منطقية تشير إلى ما إذا كان الشرط صحيحاً في هذه الحالة.
ويمكن مقارنة النصوص بالطريقة نفسها.
console.log("Aardvark" < "Zoroaster")
// → true
ترتيب النصوص أبجدي تقريباً، لكنه ليس تماماً ما تتوقعه في قاموس: فالحروف الكبيرة دائماً «أصغر» من الحروف الصغيرة، لذا "Z" < "a"، كما أن المحارف غير الأبجدية (! و- وما شابه) مشمولة أيضاً في الترتيب. وعند مقارنة النصوص، تمر JavaScript على المحارف من اليسار إلى اليمين، مقارنةً رموز Unicode واحداً واحداً.
وثمة معاملات مشابهة أخرى: >= (أكبر من أو يساوي)، و<= (أصغر من أو يساوي)، و== (يساوي)، و!= (لا يساوي).
console.log("Garnet" != "Ruby")
// → true
console.log("Pearl" == "Amethyst")
// → false
لا توجد في JavaScript سوى قيمة واحدة لا تساوي نفسها، وهي NaN («ليس عدداً»).
console.log(NaN == NaN)
// → false
من المفترض أن يشير NaN إلى نتيجة حساب لا معنى له، وبالتالي فهو لا يساوي نتيجة أي حساب لا معنى له آخر.
المعاملات المنطقية
ثمة أيضاً بعض العمليات التي يمكن تطبيقها على القيم المنطقية نفسها. وتدعم JavaScript ثلاثة معاملات منطقية: و (and)، وأو (or)، وليس (not). ويمكن استخدامها «للتفكير» بالقيم المنطقية.
يمثل المعامل && و المنطقية. وهو معامل ثنائي، ونتيجته true فقط إذا كانت القيمتان المعطاتان له صحيحتين معاً.
console.log(true && false)
// → false
console.log(true && true)
// → true
ويشير المعامل || إلى أو المنطقية. وهو ينتج true إذا كانت إحدى القيمتين المعطاتين له صحيحة.
console.log(false || true)
// → true
console.log(false || false)
// → false
وتُكتب ليس بعلامة تعجب (!). وهي معامل أحادي يقلب القيمة المعطاة له — فـ!true ينتج false و!false يعطي true.
عند خلط هذه المعاملات المنطقية مع المعاملات الحسابية وغيرها، ليس من الواضح دائماً متى تلزم الأقواس. عملياً، يمكنك عادةً الاكتفاء بمعرفة أنه من بين المعاملات التي رأيناها حتى الآن، تمتلك || أدنى أسبقية، ثم تأتي &&، ثم معاملات المقارنة (> و== وما شابه)، ثم البقية. وقد اختير هذا الترتيب بحيث تقلّ الأقواس اللازمة إلى أدنى حد في تعبيرات نموذجية مثل التالي:
1 + 1 == 2 && 10 * 10 > 50
آخر معامل منطقي سننظر إليه ليس أحادياً ولا ثنائياً، بل ثلاثياً (ternary)، إذ يعمل على ثلاث قيم. ويُكتب بعلامة استفهام ونقطتين، هكذا:
console.log(true ? 1 : 2);
// → 1
console.log(false ? 1 : 2);
// → 2
يسمى هذا المعامل الشرطي (conditional) (أو أحياناً مجرد المعامل الثلاثي لأنه المعامل الوحيد من هذا النوع في اللغة). ويستخدم المعامل القيمة الواقعة يسار علامة الاستفهام ليقرر أي القيمتين الأخريين «يختار». فإذا كتبت a ? b : c، ستكون النتيجة b عندما تكون a صحيحة، وc خلاف ذلك.
القيم الفارغة
ثمة قيمتان خاصتان، تُكتبان null وundefined، تُستخدمان للإشارة إلى غياب قيمة ذات معنى. وهما قيمتان بحد ذاتهما، لكنهما لا تحملان أي معلومات.
وكثير من العمليات في اللغة التي لا تنتج قيمة ذات معنى تُرجع undefined لمجرد أنه لا بد لها من إرجاع بعض القيم.
والفرق في المعنى بين undefined وnull هو من قبيل المصادفة في تصميم JavaScript، وهو لا يهم في معظم الأوقات. وفي الحالات التي يتعين عليك فيها حقاً الاهتمام بهذه القيم، أنصحك بالتعامل معها على أنها قابلة للتبادل في الغالب.
التحويل التلقائي للأنواع
في المقدمة، ذكرت أن JavaScript تبذل جهداً خاصاً لقبول أي برنامج تقريباً تعطيها إياه، حتى البرامج التي تفعل أشياء غريبة. ويتجلى هذا بشكل جميل في التعبيرات التالية:
console.log(8 * null)
// → 0
console.log("5" - 1)
// → 4
console.log("5" + 1)
// → 51
console.log("five" * 2)
// → NaN
console.log(false == 0)
// → true
عندما يُطبَّق معامل على نوع القيمة «الخاطئ»، تحوّل JavaScript تلك القيمة بهدوء إلى النوع الذي تحتاجه، وفق مجموعة من القواعد التي غالباً ما لا تكون ما تريده أو تتوقعه. ويسمى هذا إكراه النوع (type coercion). فـnull في التعبير الأول تصبح 0، و"5" في التعبير الثاني تصبح 5 (من نص إلى عدد). لكن في التعبير الثالث، تحاول + دمج النصوص قبل الجمع العددي، لذا تُحوَّل 1 إلى "1" (من عدد إلى نص).
وعندما يُحوَّل شيء لا يقابل عدداً بطريقة واضحة (مثل "five" أو undefined) إلى عدد، تحصل على القيمة NaN. وتظل العمليات الحسابية اللاحقة على NaN تنتج NaN، لذا إذا وجدت نفسك تحصل على إحداها في موضع غير متوقع، فابحث عن تحويلات أنواع عرضية.
عند مقارنة قيم من النوع نفسه باستخدام المعامل ==، تكون النتيجة سهلة التوقع: يجب أن تحصل على true عندما تكون القيمتان متماثلتين، باستثناء حالة NaN. لكن عندما تختلف الأنواع، تستخدم JavaScript مجموعة معقدة ومربكة من القواعد لتحديد ما يجب فعله. في معظم الحالات، تحاول فقط تحويل إحدى القيمتين إلى نوع القيمة الأخرى. غير أنه عندما يظهر null أو undefined على أي من جانبي المعامل، فإنه ينتج true فقط إذا كان الجانبان أحدهما null أو undefined.
console.log(null == undefined);
// → true
console.log(null == 0);
// → false
هذا السلوك مفيد غالباً. فعندما تريد اختبار ما إذا كانت قيمة تحمل قيمة حقيقية بدلاً من null أو undefined، يمكنك مقارنتها بـnull باستخدام المعامل == أو !=.
ماذا لو أردت اختبار ما إذا كان شيء ما يشير إلى القيمة الدقيقة false؟ التعبيرات مثل 0 == false و"" == false صحيحة أيضاً بسبب التحويل التلقائي للأنواع. وعندما لا تريد حدوث أي تحويلات للنوع، ثمة معاملان إضافيان: === و!==. الأول يختبر ما إذا كانت قيمة مساوية تماماً للأخرى، والثاني يختبر ما إذا كانت غير مساوية تماماً. لذا فإن "" === false هي false، كما هو متوقع.
أنصح باستخدام معاملات المقارنة الثلاثية دفاعياً لمنع تحويلات الأنواع غير المتوقعة من الإيقاع بك. لكن عندما تكون واثقاً من أن النوعين على الجانبين سيكونان متماثلين، فلا مشكلة في استخدام المعاملات الأقصر.
التقييم القصير للمعاملات المنطقية
تعالج المعاملات المنطقية && و|| القيم ذات الأنواع المختلفة بطريقة غريبة. فهي تحوّل القيمة على جانبها الأيسر إلى النوع Boolean لتقرر ما يجب فعله، لكنها، حسب المعامل ونتيجة ذلك التحويل، تُرجع إما القيمة اليسرى الأصلية أو القيمة اليمنى.
فالمعامل ||، مثلاً، يُرجع القيمة الواقعة إلى يساره عندما يمكن تحويل تلك القيمة إلى true، ويُرجع القيمة الواقعة إلى يمينه خلاف ذلك. ولهذا الأثر المتوقع عندما تكون القيم منطقية، ويفعل شيئاً مماثلاً مع قيم من أنواع أخرى.
console.log(null || "user")
// → user
console.log("Agnes" || "user")
// → Agnes
يمكننا استخدام هذه الوظيفة كوسيلة للرجوع إلى قيمة افتراضية. فإذا كانت لديك قيمة قد تكون فارغة، يمكنك وضع || بعدها مع قيمة بديلة. وإذا أمكن تحويل القيمة الأولية إلى false، ستحصل على البديل بدلاً منها. وتنص قواعد تحويل النصوص والأعداد إلى قيم منطقية على أن 0 وNaN والنص الفارغ ("") تُعدّ false، بينما تُعدّ كل القيم الأخرى true. وهذا يعني أن 0 || -1 ينتج -1، و"" || "!?" يعطي "!?".
ويشبه المعامل ?? المعامل || لكنه يُرجع القيمة على اليمين فقط إذا كانت القيمة على اليسار null أو undefined، لا إذا كانت قيمة أخرى يمكن تحويلها إلى false. وغالباً ما يكون هذا أفضل من سلوك ||.
console.log(0 || 100);
// → 100
console.log(0 ?? 100);
// → 0
console.log(null ?? 100);
// → 100
ويعمل المعامل && بالمثل لكن بالاتجاه المعاكس. فعندما تكون القيمة الواقعة إلى يساره شيئاً يُحوَّل إلى false، يُرجع تلك القيمة، وإلا يُرجع القيمة الواقعة إلى يمينه.
وثمة خاصية مهمة أخرى لهذين المعاملين: وهي أن الجزء الواقع إلى يمينهما لا يُقيَّم إلا عند الحاجة. ففي حالة true || X، مهما كان X — حتى لو كان قطعة برنامج تفعل شيئاً فظيعاً — ستكون النتيجة true، ولن يُقيَّم X أبداً. وينطبق الأمر نفسه على false && X، فهي false وستتجاهل X. ويسمى هذا التقييم القصير (short-circuit evaluation).
ويعمل المعامل الشرطي بطريقة مشابهة. فمن القيمتين الثانية والثالثة، لا تُقيَّم إلا القيمة المختارة.
الخلاصة
نظرنا في هذا الفصل إلى أربعة أنواع من قيم JavaScript: الأعداد والنصوص والقيم المنطقية والقيم غير المعرّفة. وتُنشأ مثل هذه القيم بكتابة اسمها (true، null) أو قيمتها (13، "abc").
ويمكنك دمج القيم وتحويلها بالمعاملات. رأينا معاملات ثنائية للحساب (+ و- و* و/ و%)، ودمج النصوص (+)، والمقارنة (== و!= و=== و!== و< و> و<= و>=)، والمنطق (&& و|| و??)، إضافة إلى عدة معاملات أحادية (- لنفي عدد، و! للنفي المنطقي، وtypeof لمعرفة نوع قيمة) ومعامل ثلاثي (?:) لاختيار إحدى قيمتين بناءً على قيمة ثالثة.
يمنحك هذا معلومات كافية لاستخدام JavaScript كآلة حاسبة جيبية، لكن ليس أكثر من ذلك بكثير. وسيبدأ الفصل التالي في ربط هذه التعبيرات معاً في برامج أساسية.