Apa itu "ini" dan apa yang dimakannya


Foto oleh Sebastian Herrmann .

Selamat siang teman-teman!

Saya mempersembahkan kepada Anda terjemahan artikel Daniel James “Apa itu 'ini'? Kenapa begitu? ” .

Apa itu "ini" dan apa yang dimakannya


Ketika saya mulai belajar JavaScript, konsep ini bagi saya sangat membingungkan.

Pendahuluan


Pertumbuhan cepat dalam popularitas JS sebagian dapat dijelaskan oleh ambang masuk yang rendah. Fitur seperti fungsi dan ini biasanya berfungsi seperti yang diharapkan. Untuk menjadi seorang profesional di JS, Anda tidak perlu tahu banyak detail kecil dan detail (saya akan membantahnya - kira-kira Per.). Tetapi sekali, setiap pengembang menemukan kesalahan yang disebabkan oleh nilai ini.

Setelah itu, Anda memiliki keinginan untuk memahami cara kerjanya di JS. Apakah ini konsep pemrograman berorientasi objek (OOP)? Apakah JS Bahasa Pemrograman Berorientasi Objek (OOJP)? Jika Anda "google" ini, Anda akan menerima tanggapan menyebutkan beberapa prototipe. Prototipe seperti apa? Untuk apa kata kunci "baru" digunakan sebelum kelas muncul di JS?

Semua hal ini berkaitan erat. Tetapi sebelum menjelaskan bagaimana ini bekerja, saya akan membiarkan diri saya sedikit menyimpang. Saya ingin berbicara sedikit tentang mengapa JS itu apa adanya.

OOP di JS


Paradigma pemrograman prototipe (warisan) dalam JS adalah salah satu keunggulan OOP. Bahkan sebelum munculnya kelas JS, ada OOJP. JS adalah bahasa sederhana yang hanya menggunakan beberapa hal dari OOP. Yang paling penting adalah fungsi, penutup, ini, prototipe, objek literal, dan kata kunci baru.

Enkapsulasi dan Dapat Digunakan Kembali dengan Penutup


Mari kita buat kelas Counter. Kelas ini harus memiliki metode untuk mengatur ulang dan menambah penghitung. Kita dapat menulis sesuatu seperti ini:

function Counter(initialValue = 0){ let _count = initialValue return { reset: function(){ _count = 0 }, next: function(){ return ++_count } } } const myCounter = Counter() console.log(myCounter.next()) // 1 

Dalam hal ini, kami membatasi diri untuk menggunakan fungsi dan objek literal tanpa ini atau baru. Ya, kami sudah mendapatkan sesuatu dari OOP. Kami memiliki kesempatan untuk membuat instance Counter baru. Setiap instance Counter memiliki jumlah variabel internal sendiri. Kami menerapkan enkapsulasi dan menggunakan kembali dengan cara yang murni fungsional.

Masalah kinerja


Misalkan kita sedang menulis sebuah program yang menggunakan sejumlah besar penghitung. Setiap penghitung akan memiliki metode reset sendiri dan selanjutnya (Penghitung (). Reset! = Penghitung (). Reset). Membuat penutupan seperti itu untuk setiap metode dari setiap instance akan membutuhkan banyak memori! Arsitektur seperti itu "tidak berkelanjutan." Oleh karena itu, kita perlu menemukan cara untuk menyimpan di setiap instance Counter hanya referensi ke metode yang digunakannya (pada kenyataannya, inilah yang dilakukan semua OOJP, seperti Java).

Kami dapat memecahkan masalah ini sebagai berikut (tanpa melibatkan fitur bahasa tambahan):

 let Counter = { reset: function(counter){ counter._count = 0 }, next: function(counter){ return ++counter._count }, new: function(initialValue = 0){ return { _count: initialValue } } } const myCounter = Counter.new() console.log(Counter.next(myCounter)) // 1 

Pendekatan ini memecahkan masalah kinerja, tetapi kami harus membuat kompromi yang serius, yang terdiri dari kebutuhan akan partisipasi programmer tingkat tinggi dalam pelaksanaan program (dalam memastikan bahwa kode tersebut berfungsi). Tanpa alat tambahan, kita harus puas dengan pendekatan ini.

Ini bergegas menyelamatkan


Kami menulis ulang contoh kami menggunakan ini:

 let Counter = { reset: function(){ this._count = 0 }, next: function(){ return ++this._count }, new: function(initialValue = 0){ return { _count: initialValue, //          reset: Counter.reset, next: Counter.next } } } const myCounter = Counter.new() myCounter.next() // ,  reset     myCounter (Counter.new()).reset() console.log(myCounter.next()) // 2 

Perhatikan bahwa kami masih membuat reset sederhana dan fungsi berikutnya (Counter.new (). Reset == Counter.new (). Reset). Dalam contoh sebelumnya, agar program dapat bekerja, kami dipaksa untuk memberikan deskriptor contoh untuk metode yang diterapkan bersama. Sekarang kita panggil myCounter.next () dan lihat contoh menggunakan ini. Tetapi bagaimana cara kerjanya? Reset dan selanjutnya dideklarasikan di objek Counter. Bagaimana JS tahu apa ini merujuk ketika memanggil fungsi?

Fungsi Memanggil di JS


Anda tahu betul bahwa fungsi-fungsi di JS memiliki metode panggilan (ada juga metode yang berlaku; perbedaan antara metode ini tidak signifikan. Perbedaannya adalah bagaimana kami meneruskan parameter: di terapkan sebagai array, dalam panggilan dipisahkan oleh koma - kira-kira Per.) . Menggunakan panggilan, Anda memutuskan apa artinya ini ketika fungsi dipanggil:

 const myCounter = Counter.new() Counter.next.call(myCounter) 

Ini sebenarnya yang notasi titik lakukan di belakang layar ketika kita memanggil fungsi. lhs.fn () identik dengan fn.call (lhs).

Jadi, ini adalah pengidentifikasi khusus yang diatur ketika fungsi dipanggil.

Masalah mulai


Katakanlah Anda ingin membuat penghitung dan menambah nilainya setiap detik. Inilah cara melakukannya:

 const myCounter = Counter.new() setInterval(myCounter.next, 1000) //    console.log(`Why is ${myCounter.next()} still 0?`) //  myCounter.next()    0? 

Apakah Anda melihat kesalahan di sini? Ketika setInterval dimulai, nilai ini tidak terdefinisi, jadi tidak ada yang terjadi. Masalah ini dapat dipecahkan sebagai berikut:

 const myCounter = Counter.new() setInterval(function(){ myCounter.next() }, 1000) 

Sedikit tentang mengikat


Ada cara lain untuk mengatasi masalah ini:

 function bindThis(fn, _this){ return function(...args){ return fn.call(_this, ...args) } } const myCounter = Counter.new() setInterval(bindThis(myCounter.next, myCounter), 1000) 

Menggunakan fungsi bindThis pabrik, kita dapat memastikan bahwa Counter.next selalu memanggil myCounter karena ini, terlepas dari bagaimana fungsi baru dipanggil. Bahkan, kami tidak mengubah fungsi Counter.next. JS memiliki metode binding bawaan. Oleh karena itu, kita dapat menulis ulang contoh di atas seperti ini: setInterval (myCounter.next.bind (myCounter), 1000).

Kami bekerja dengan prototipe


Saat ini, kami memiliki kelas Counter yang bagus, tetapi masih sedikit "bengkok". Ini adalah baris berikut:

 // ... reset: Counter.reset, next: Counter.next, // ... 

Kami membutuhkan cara yang lebih baik untuk berbagi metode kelas dengan instansnya. Prototipe melakukan tugasnya dengan sangat baik. Jika Anda merujuk ke properti fungsi atau objek yang tidak ada, JS akan mencari properti ini dalam prototipe fungsi atau objek ini (kemudian dalam prototipe prototipe dan seterusnya ke Object.prototype yang terletak di bagian atas rantai prototipe - sekitar Trans.). Anda bisa mendefinisikan prototipe suatu objek menggunakan Object.setPrototypeOf. Mari kita menulis ulang kelas Counter kita menggunakan prototipe:

 let Counter = { reset: function(){ this._count = 0 }, next: function(){ return ++this._count }, new: function(initialValue = 0){ this._count = initialValue } } function newInstanceOf(klass, ...args){ //       "klass",  "class"   const instance = {} Object.setPrototypeOf(instance, klass) instance.new(...args) return instance } const myCounter = newInstanceOf(Counter) console.log(myCounter.next()) // 1 

Kata kunci "baru"


Menggunakan setPrototypeOf sangat mirip dengan cara kerja operator "baru". Perbedaannya adalah bahwa baru akan menggunakan konstruktor prototipe dari fungsi yang dikirimkan. Oleh karena itu, alih-alih membuat objek untuk metode kami, kami meneruskannya ke prototipe konstruktor fungsi:

 function Counter(initialValue = 0){ this._count = initialValue } Counter.prototype.reset = function(){ this._count = 0 } Counter.prototype.next = function(){ return ++this._count } const myCounter = new Counter() console.log(`${myCounter.next()}`) // 1 

Akhirnya, kami memiliki kode dalam bentuk yang dapat ditemukan dalam praktek. Sebelum kelas muncul di JS, ini adalah pendekatan standar untuk membuat dan menginisialisasi kelas.

Kata kunci "kelas"


Saya harap sekarang Anda mengerti mengapa kami menggunakan prototipe konstruktor fungsi dan bagaimana ini bekerja dalam metode fungsi. Namun, kode kami dapat ditingkatkan. Untungnya, hari ini di JS ada cara yang lebih baik untuk mendeklarasikan kelas:

 class Counter { reset(){ this._count = 0 } next(){ return ++this._count } constructor(initialValue = 0){ this._count = initialValue } } const myCounter = new Counter() console.log(`${myCounter.next()}`) // 1 

Kata kunci "kelas" tidak melakukan apa-apa terutama di bawah "kucing". Anda dapat menganggapnya sebagai gula sintaksis, pembungkus untuk pendekatan "prototipe". Jika Anda menjalankan transporter yang berorientasi pada ES3, Anda akan mendapatkan sesuatu seperti ini:

 var Counter = /** @class **/ (function(){ function Counter(initialValue){ if(initialValue === void 0) { initialValue = 0 } this._count = initialValue } Counter.prototype.reset = function(){ this._count = 0 } Counter.prototype.next = function(){ ++this._count } return Counter }()); var myCounter = new Counter() console.log(myCounter.next()) 

Perhatikan bahwa transpiler menghasilkan kode yang hampir identik dengan contoh sebelumnya.

Fungsi panah


Jika Anda telah menulis kode dalam JS selama 5 tahun terakhir, Anda mungkin terkejut bahwa saya menyebutkan fungsi panah. Saran saya: selalu gunakan fungsi panah sampai Anda benar-benar membutuhkan fungsi reguler. Kebetulan definisi dari konstruktor dan metode kelas adalah kasus ketika kita harus menggunakan fungsi biasa. Salah satu fitur fungsi panah adalah kebingungan.

Ini dalam fungsi panah


Beberapa orang mungkin berasumsi bahwa fungsi panah mengambil nilai saat ini ketika dibuat. Ini tidak benar dari sudut pandang teknis (arti dari ini tidak didefinisikan, diambil dari lingkungan leksikal), tetapi ini adalah model mental yang baik. Fungsi panah seperti ini:

 const myArrowFunction = () => { this.doSomething() } 

Anda dapat menulis ulang seperti ini:
 const _this = this const myRegularFunction = function(){ _this.doSomething() } 

Terima kasih atas perhatian anda Semua yang terbaik

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


All Articles