(a==1 && a==2 && a==3) true؟ سحر JavaScript

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: السؤال الذي هز Stack Overflow

\n\n

تخيل الموقف: أنت في مقابلة عمل، ويسألك المحاور بابتسامة ماكرة: «هل يمكن للتعبير a == 1 && a == 2 && a == 3 أن يعيد true في أي وقت؟». رد الفعل الأول سيكون: «بالطبع لا! متغير واحد لا يمكن أن يساوي ثلاثة أرقام مختلفة في نفس الوقت».

\n\n

ولكن، إذا كنت على دراية بـ JavaScript بشكل أعمق، فأنت تعلم أن هذا اللغة قادرة على أشياء غير متوقعة. هذا السؤال حصد آلاف المشاهدات على Stack Overflow وأصبح من الكلاسيكيات لاختبار فهم تحويل الأنواع (type coercion) و الـ getters و الأغلفة الكائنية (object wrappers) في JS.

\n\n

في هذا المقال، لن نثبت فقط أن هذا ممكن، بل سنشرح 5 طرق مختلفة لجعل (a==1 && a==2 && a==3) يعيد true. ستتعلم كيف تعمل المقارنة غير الصارمة (==)، وما الفرق بينها وبين المقارنة الصارمة (===)، وكيف يمكنك التحكم بها.

\n\n

هذا المقال مفيد لكل من المبتدئين الذين يريدون فهم JavaScript بشكل أعمق، والمطورين ذوي الخبرة الذين يستعدون للمقابلات. هيا بنا!

\n\n

الطريقة الأولى: سحر valueOf (الأكثر شهرة)

\n\n

الطريقة الأكثر أناقة والتي يتم تداولها كثيرًا هي استخدام كائن مخصص (custom object) مع إعادة تعريف دالة valueOf. في JavaScript، عندما تقارن كائنًا بقيمة أولية (primitive) باستخدام ==، يقوم المحرك تلقائيًا باستدعاء valueOf() (أو toString()) لتحويل الكائن إلى قيمة أولية.

\n\n

سنقوم بإنشاء كائن عداد، يزيد قيمته بمقدار 1 في كل مرة يتم فيها استدعاء valueOf.

\n\n
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
\n\n

هذا مثال مثالي على تحويل الأنواع القسري (type coercion) أثناء العمل. هذه الآلية هي ما يجعل JavaScript لغة مرنة وخطيرة في نفس الوقت.

\n\n

الطريقة الثانية: toString — توأم valueOf

\n\n

إذا قمت بإعادة تعريف toString بدلاً من valueOf، فستحصل على نفس النتيجة. في JavaScript، عند المقارنة غير الصارمة، يمكن استخدام أي من هاتين الدالتين لتحويل الكائن.

\n\n
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 له أولوية في المقارنات النصية. ولكن في حالتنا، كلاهما مناسب تمامًا.

\n\n

الطريقة الثالثة: الـ Getters في النطاق العام (Global Scope)

\n\n

كثيرون لا يدركون أن المتغيرات العامة (global variables) في JavaScript هي في الواقع خصائص للكائن العام (window في المتصفح، global في Node.js). ويمكن تعريف الخصائص باستخدام الـ getters.

\n\n

يمكننا تعريف getter للمتغير a مباشرة على الكائن العام:

\n\n
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 قيمة جديدة.

المدونات

توصيات الكتب