مقدمة: السؤال الذي هز Stack Overflow
\n\nتخيل الموقف: أنت في مقابلة عمل، ويسألك المحاور بابتسامة ماكرة: «هل يمكن للتعبير a == 1 && a == 2 && a == 3 أن يعيد true في أي وقت؟». رد الفعل الأول سيكون: «بالطبع لا! متغير واحد لا يمكن أن يساوي ثلاثة أرقام مختلفة في نفس الوقت».
ولكن، إذا كنت على دراية بـ JavaScript بشكل أعمق، فأنت تعلم أن هذا اللغة قادرة على أشياء غير متوقعة. هذا السؤال حصد آلاف المشاهدات على Stack Overflow وأصبح من الكلاسيكيات لاختبار فهم تحويل الأنواع (type coercion) و الـ getters و الأغلفة الكائنية (object wrappers) في JS.
\n\nفي هذا المقال، لن نثبت فقط أن هذا ممكن، بل سنشرح 5 طرق مختلفة لجعل (a==1 && a==2 && a==3) يعيد true. ستتعلم كيف تعمل المقارنة غير الصارمة (==)، وما الفرق بينها وبين المقارنة الصارمة (===)، وكيف يمكنك التحكم بها.
هذا المقال مفيد لكل من المبتدئين الذين يريدون فهم JavaScript بشكل أعمق، والمطورين ذوي الخبرة الذين يستعدون للمقابلات. هيا بنا!
\n\nالطريقة الأولى: سحر valueOf (الأكثر شهرة)
\n\nالطريقة الأكثر أناقة والتي يتم تداولها كثيرًا هي استخدام كائن مخصص (custom object) مع إعادة تعريف دالة valueOf. في JavaScript، عندما تقارن كائنًا بقيمة أولية (primitive) باستخدام ==، يقوم المحرك تلقائيًا باستدعاء valueOf() (أو toString()) لتحويل الكائن إلى قيمة أولية.
سنقوم بإنشاء كائن عداد، يزيد قيمته بمقدار 1 في كل مرة يتم فيها استدعاء valueOf.
const a = {\n value: 1,\n valueOf() {\n return this.value++;\n }\n};\n\nconsole.log(a == 1 && a == 2 && a == 3); // true\n\nكيف يعمل هذا:
\n- \n
- في المقارنة الأولى
a == 1، يتم استدعاءvalueOf()، وتعيد 1، ثم تزيدvalueإلى 2. \n - في المقارنة الثانية
a == 2، يتم استدعاءvalueOf()مرة أخرى، وتعيد 2، وتصبحvalue= 3. \n - المقارنة الثالثة
a == 3تنجح وتكتمل السلسلة. \n
هذا مثال مثالي على تحويل الأنواع القسري (type coercion) أثناء العمل. هذه الآلية هي ما يجعل JavaScript لغة مرنة وخطيرة في نفس الوقت.
\n\nالطريقة الثانية: toString — توأم valueOf
\n\nإذا قمت بإعادة تعريف toString بدلاً من valueOf، فستحصل على نفس النتيجة. في JavaScript، عند المقارنة غير الصارمة، يمكن استخدام أي من هاتين الدالتين لتحويل الكائن.
const a = {\n value: 1,\n toString() {\n return this.value++;\n }\n};\n\nconsole.log(a == 1 && a == 2 && a == 3); // true\n\nالفرق بسيط: valueOf له أولوية في المقارنات الرقمية، بينما toString له أولوية في المقارنات النصية. ولكن في حالتنا، كلاهما مناسب تمامًا.
الطريقة الثالثة: الـ Getters في النطاق العام (Global Scope)
\n\nكثيرون لا يدركون أن المتغيرات العامة (global variables) في JavaScript هي في الواقع خصائص للكائن العام (window في المتصفح، global في Node.js). ويمكن تعريف الخصائص باستخدام الـ getters.
يمكننا تعريف getter للمتغير a مباشرة على الكائن العام:
let value = 1;\nObject.defineProperty(window, 'a', {\n get() {\n return value++;\n }\n});\n\nconsole.log(a == 1 && a == 2 && a == 3); // true\n\nهذه الطريقة مثيرة للاهتمام بشكل خاص لأن a تتصرف كمتغير عادي، على الرغم من أنها في الواقع خاصية مع getter. في كل مرة تصل فيها إلى a، يعيد الـ getter قيمة جديدة.