Perintah tak terduga untuk menginisialisasi kelas bawaan dalam JavaScript

Hari ini saya memiliki tugas kecil untuk refactoring kode JS, dan saya menemukan fitur bahasa yang tidak terduga, yang selama lebih dari 7 tahun pengalaman pemrograman saya dalam hal ini dibenci oleh banyak orang Bahasa tidak berpikir dan tidak menemukan.


Selain itu, saya tidak dapat menemukan apa pun di Internet Rusia atau Inggris, dan karena itu memutuskan untuk menerbitkan ini tidak terlalu lama, bukan yang paling menarik, tetapi catatan yang berguna.


Agar tidak menggunakan konstanta foo/bar tradisional dan tidak berarti, saya akan menunjukkan secara langsung pada contoh yang kita miliki dalam proyek, tetapi masih tanpa banyak logika internal dan dengan nilai-nilai palsu. Ingat bahwa semua sama, contoh-contohnya ternyata sangat sintetis.

Kami melangkah menyapu


Jadi kita punya kelas:


 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 

Semuanya logis


Dan kemudian kita perlu membuat jenis tooltip lain di mana bidang template berubah


 class SpecialTooltip extends BaseTooltip { template = 'otherTemplate' } 

Dan di sini kejutan menunggu saya, karena ketika membuat objek tipe baru, berikut ini terjadi


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

Metode render BaseTooltip.prototype.template dengan nilai BaseTooltip.prototype.template , bukan SpecialTooltip.prototype.template , seperti yang saya harapkan.


Kami melangkah menyapu dengan hati-hati, merekam video


Karena chrome DevTools tidak tahu cara menetapkan bidang kelas, Anda harus menggunakan trik untuk memahami apa yang terjadi. Menggunakan pembantu kecil, kami mencatat momen penugasan ke variabel


 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 

Dan ketika kita menerapkan pendekatan ini ke kelas yang diwarisi, kita mendapatkan yang aneh berikut:


 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 

Saya yakin bahwa bidang objek diinisialisasi pertama, dan kemudian sisa konstruktor dipanggil. Ternyata semuanya rumit.


Kami menginjak menyapu, mengecat tangkainya


Kami menyulitkan situasi dengan menambahkan parameter lain ke konstruktor, yang kami tetapkan untuk objek kami


 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 

Dan hanya cara debug ini (yah, bukan peringatan) yang diklarifikasi sedikit


Dari mana masalah ini berasal:

Sebelumnya, kode ini ditulis pada kerangka Marionette dan tampak (kondisional) seperti ini


 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' }) 

Saat menggunakan Marionette, semuanya berfungsi seperti yang saya harapkan, yaitu, metode render dipanggil dengan nilai template ditentukan di kelas, tetapi ketika menyalin logika modul ke ES6, masalah yang dijelaskan dalam artikel keluar


Hitung benjolan


Hasilnya:


Saat membuat objek dari kelas yang diwarisi, urutan apa yang terjadi adalah sebagai berikut:


  • Menginisialisasi bidang objek dari deklarasi kelas yang diwarisi
  • Eksekusi konstruktor dari kelas yang diwarisi (termasuk inisialisasi bidang di dalam konstruktor)
  • Hanya setelah inisialisasi bidang bidang dari kelas saat ini
  • Melaksanakan konstruktor dari kelas saat ini

Kami mengembalikan menyapu ke gudang


Secara khusus, dalam situasi saya, masalah dapat diselesaikan baik melalui mixin atau meneruskan templat ke konstruktor, tetapi ketika logika aplikasi mengharuskan mengesampingkan sejumlah besar bidang, ini menjadi cara yang agak kotor.


Akan menyenangkan untuk membaca di komentar saran Anda tentang cara menyelesaikan masalah dengan elegan.

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


All Articles