ترتيب غير متوقع لتهيئة الفئات الموروثة في JavaScript

اليوم ، كانت لدي مهمة صغيرة تتمثل في إعادة تكوين كود JS ، وقد صادفت ميزة غير متوقعة للغة ، والتي اكتسبت خلالها خبرة برمجية تزيد عن 7 سنوات في هذا يكره الكثيرون اللغة لم تفكر ولم تصادف.


بالإضافة إلى ذلك ، لم أتمكن من العثور على أي شيء على شبكة الإنترنت الروسية أو الإنجليزية ، وبالتالي قررت نشر هذا ليس طويلًا جدًا ، وليس المذكرة الأكثر إثارة للاهتمام ، ولكنها مفيدة.


من أجل عدم استخدام الثوابت foo/bar التقليدية التي لا معنى لها ، foo/bar مباشرة على المثال الذي كان لدينا في المشروع ، ولكن لا يزال بدون مجموعة من المنطق الداخلي وقيم مزيفة. تذكر أنه على الرغم من ذلك ، اتضح أن الأمثلة اصطناعية تمامًا.

نحن نخطو على أشعل النار


لذلك لدينا فئة:


 class BaseTooltip { template = 'baseTemplate' constructor(content) { this.render(content) } render(content) { console.log('render:', content, this.template) } } const tooltip = new BaseTooltip('content') // render: content baseTemplate 

كل شيء منطقي


ثم نحتاج إلى إنشاء نوع آخر من تلميح الأدوات يتغير فيه حقل template


 class SpecialTooltip extends BaseTooltip { template = 'otherTemplate' } 

وهنا انتظرني مفاجأة ، لأنه عند إنشاء كائن من نوع جديد ، يحدث ما يلي


 const specialTooltip = new SpecialTooltip('otherContent') // render: otherContent baseTemplate // ^  

تم BaseTooltip.prototype.template طريقة التجسيد بالقيمة BaseTooltip.prototype.template ، وليس SpecialTooltip.prototype.template ، كما كنت أتوقع.


نحن خطوة على أشعل النار بعناية ، تصوير الفيديو


نظرًا لأن chrome DevTools لا يعرف كيفية تعيين حقول الفصل ، يجب عليك اللجوء إلى الحيل لفهم ما يحدث. باستخدام مساعد صغير ، نقوم بتسجيل لحظة التعيين إلى متغير


 function logAndReturn(value) { console.log(`set property=${value}`) return value } class BaseTooltip { template = logAndReturn('baseTemplate') constructor(content) { console.log(`call constructor with property=${this.template}`) this.render(content) } render(content) { console.log(content, this.template) } } const tooltip = new BaseTooltip('content') // set property=baseTemplate // called constructor BaseTooltip with property=baseTemplate // render: content baseTemplate 

وعندما نطبق هذا النهج على الصف الموروث ، فإننا نحصل على الغريب التالي:


 class SpecialTooltip extends BaseTooltip { template = logAndReturn('otherTemplate') } const tooltip = new SpecialTooltip('content') // set property=baseTemplate // called constructor SpecialTooltip with property=baseTemplate // render: content baseTemplate // set property=otherTemplate 

كنت متأكدًا من تهيئة حقول الكائن أولاً ، ثم يتم استدعاء باقي المنشئ. اتضح أن كل شيء أصعب.


نحن خطوة على أشعل النار ، ورسم ساق


نقوم بتعقيد الموقف من خلال إضافة معلمة أخرى إلى المُنشئ ، والتي نخصصها لكائننا


 class BaseTooltip { template = logAndReturn('baseTemplate') constructor(content, options) { this.options = logAndReturn(options) // <---   console.log(`called constructor ${this.constructor.name} with property=${this.template}`) this.render(content) } render(content) { console.log(content, this.template, this.options) // <---   } } class SpecialTooltip extends BaseTooltip { template = logAndReturn('otherTemplate') } const tooltip = new SpecialTooltip('content', 'someOptions') //    : // set property=baseTemplate // set property=someOptions // called constructor SpecialTooltip with property=baseTemplate // render: content baseTemplate someOptions // set property=otherTemplate 

وفقط هذه الطريقة من التصحيح (حسنا ، لا تنبيهات) قليلا


من أين جاءت هذه المشكلة؟

في السابق ، تم كتابة هذا الرمز على إطار Marionette وبدا (مشروطًا) مثل هذا


 const BaseTooltip = Marionette.Object.extend({ template: 'baseTemplate', initialize(content) { this.render(content) }, render(content) { console.log(content, this.template) }, }) const SpecialTooltip = BaseTooltip.extend({ template: 'otherTemplate' }) 

عند استخدام Marionette ، كان كل شيء يعمل كما كنت أتوقع ، أي تم استدعاء طريقة render بقيمة template المحددة في الفصل ، ولكن عند نسخ منطق الوحدة إلى ES6 ، خرجت المشكلة الموضحة في المقالة


عد المطبات


النتيجة:


عند إنشاء كائن من فئة موروثة ، يكون ترتيب ما يحدث كما يلي:


  • تهيئة حقول الكائن من تعريف فئة موروثة
  • تنفيذ مُنشئ الفئة الموروثة (بما في ذلك تهيئة الحقول داخل المُنشئ)
  • فقط بعد هذا التهيئة لحقول الكائن من الفئة الحالية
  • تنفيذ منشئ الفصل الحالي

نعود أشعل النار إلى الحظيرة


على وجه التحديد ، في وضعي ، يمكن حل المشكلة إما من خلال mixins أو تمرير القالب إلى المُنشئ ، ولكن عندما يتطلب منطق التطبيق تجاوز عدد كبير من الحقول ، يصبح هذا طريقة قذرة إلى حد ما.


سيكون من الجيد أن تقرأ في التعليقات اقتراحاتك حول كيفية حل المشكلة بأناقة.

Source: https://habr.com/ru/post/ar461399/


All Articles