JavaScript berorientasi objek dalam istilah sederhana





Selamat siang teman!



Ada 4 cara dalam JavaScript untuk membuat objek:



  • Fungsi konstruktor
  • Kelas (class)
  • Objek yang ditautkan ke objek lain (OLOO)
  • Fungsi pabrik


Metode mana yang sebaiknya Anda gunakan? Yang mana yang terbaik?



Untuk menjawab pertanyaan ini, kami tidak hanya akan mempertimbangkan setiap pendekatan secara terpisah, tetapi juga membandingkan kelas dan fungsi pabrik sesuai dengan kriteria berikut: pewarisan, enkapsulasi, kata kunci "ini", penangan kejadian.



Mari kita mulai dengan apa itu Pemrograman Berorientasi Objek (OOP).



Apa itu OOP?



Pada dasarnya, OOP adalah cara penulisan kode yang memungkinkan Anda membuat objek menggunakan satu objek. Ini juga merupakan inti dari pola desain Konstruktor. Objek bersama biasanya disebut cetak biru, cetak biru, atau cetak biru, dan objek yang dibuatnya adalah instance.



Setiap instance memiliki properti yang diwarisi dari induk dan propertinya sendiri. Misalnya, jika kita memiliki proyek Manusia, kita dapat membuat instance dengan nama berbeda berdasarkan itu.



Aspek kedua dari OOP adalah menyusun kode ketika kami memiliki beberapa proyek dengan level yang berbeda. Ini disebut warisan atau subclassing.



Aspek ketiga OOP adalah enkapsulasi, ketika kita menyembunyikan detail implementasi dari pihak luar, membuat variabel dan fungsi tidak dapat diakses dari luar. Ini adalah inti dari pola desain Modul dan Fasad.



Mari kita lanjutkan dengan cara membuat objek.



Metode pembuatan objek



Fungsi konstruktor


Konstruktor adalah fungsi yang menggunakan kata kunci "ini".



    function Human(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }


ini memungkinkan Anda untuk menyimpan dan mengakses nilai unik dari instance yang sedang dibuat. Contoh dibuat menggunakan kata kunci "baru".



const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

const zell = new Human('Zell', 'Liew')
console.log(zell.firstName) // Zell
console.log(zell.lastName) // Liew


Kelas


Kelas adalah abstraksi ("gula sintaksis") di atas fungsi konstruktor. Mereka membuatnya lebih mudah untuk membuat instance.



    class Human {
        constructor(firstName, lastName) {
            this.firstName = firstName
            this.lastName = lastName
        }
    }


Perhatikan bahwa konstruktor berisi kode yang sama dengan fungsi konstruktor di atas. Kami harus melakukan ini untuk menginisialisasi ini. Kita bisa menghilangkan konstruktor jika kita tidak perlu menetapkan nilai awal.



Sekilas, kelas tampak lebih kompleks daripada konstruktor - Anda harus menulis lebih banyak kode. Pegang kudamu dan jangan langsung mengambil kesimpulan. Kelas itu keren. Anda akan mengerti mengapa nanti.



Contoh juga dibuat menggunakan kata kunci "baru".



const chris = new Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


Menghubungkan objek


Metode membuat objek ini diusulkan oleh Kyle Simpson. Dalam pendekatan ini, kami mendefinisikan proyek sebagai objek biasa. Kemudian, dengan menggunakan metode (yang biasanya disebut init, tetapi ini tidak diperlukan, tidak seperti konstruktor di kelas), kami menginisialisasi instance.



const Human = {
    init(firstName, lastName) {
        this.firstName = firstName
        this.lastName = lastName
    }
}


Object.create digunakan untuk membuat sebuah instance. Setelah instansiasi, init dipanggil.



const chris = Object.create(Human)
chris.init('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


Kode dapat ditingkatkan sedikit dengan mengembalikan ini ke init.



const Human = {
  init () {
    // ...
    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


Fungsi pabrik


Fungsi pabrik adalah fungsi yang mengembalikan suatu objek. Benda apa pun bisa dikembalikan. Anda bahkan dapat mengembalikan sebuah instance dari class atau object binding.



Berikut adalah contoh sederhana dari fungsi pabrik.



function Human(firstName, lastName) {
    return {
        firstName,
        lastName
    }
}


Kami tidak memerlukan kata kunci "ini" untuk membuat sebuah instance. Kami hanya memanggil fungsinya.



const chris = Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier


Sekarang mari kita lihat cara menambahkan properti dan metode.



Mendefinisikan properti dan metode



Metode adalah fungsi yang dideklarasikan sebagai properti dari suatu objek.



    const someObject = {
        someMethod () { /* ... */ }
    }


Di OOP, ada dua cara untuk mendefinisikan properti dan metode:



  • Dalam sekejap
  • Dalam prototipe


Mendefinisikan properti dan metode dalam konstruktor


Untuk mendefinisikan properti pada sebuah instance, Anda harus menambahkannya ke fungsi konstruktor. Pastikan untuk menambahkan properti ke ini.



function Human (firstName, lastName) {
  //  
  this.firstName = firstName
  this.lastname = lastName

  //  
  this.sayHello = function () {
    console.log(`Hello, I'm ${firstName}`)
  }
}

const chris = new Human('Chris', 'Coyier')
console.log(chris)






Metode biasanya ditentukan dalam prototipe, karena ini menghindari pembuatan fungsi untuk setiap instance, mis. Mengizinkan semua instance berbagi satu fungsi (disebut fungsi bersama atau didistribusikan).



Untuk menambahkan properti ke prototipe, gunakan prototipe.



function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastname = lastName
}

//    
Human.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.firstName}`)
}






Membuat banyak metode bisa jadi membosankan.



//    
Human.prototype.method1 = function () { /*...*/ }
Human.prototype.method2 = function () { /*...*/ }
Human.prototype.method3 = function () { /*...*/ }


Anda dapat membuat hidup Anda lebih mudah dengan Object.assign.



Object.assign(Human.prototype, {
  method1 () { /*...*/ },
  method2 () { /*...*/ },
  method3 () { /*...*/ }
})


Mendefinisikan properti dan metode di kelas


Properti instance dapat ditentukan dalam konstruktor.



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
      this.lastname = lastName

      this.sayHello = function () {
        console.log(`Hello, I'm ${firstName}`)
      }
  }
}






Properti prototipe didefinisikan setelah konstruktor sebagai fungsi normal.



class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}






Membuat beberapa metode di kelas lebih mudah daripada di konstruktor. Kami tidak membutuhkan Object.assign untuk ini. Kami hanya menambahkan fitur lainnya.



class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  method1 () { /*...*/ }
  method2 () { /*...*/ }
  method3 () { /*...*/ }
}


Mendefinisikan properti dan metode saat mengikat objek


Untuk mendefinisikan properti untuk sebuah instance, kami menambahkan properti ke dalamnya.



const Human = {
  init (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.sayHello = function () {
      console.log(`Hello, I'm ${firstName}`)
    }

    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris)






Metode prototipe didefinisikan sebagai objek biasa.



const Human = {
  init () { /*...*/ },
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}






Mendefinisikan properti dan metode dalam fungsi pabrik (FF)


Properti dan metode dapat dimasukkan dalam objek yang dikembalikan.



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}






Saat menggunakan FF, Anda tidak dapat menentukan properti prototipe. Jika Anda membutuhkan properti seperti ini, Anda dapat mengembalikan instance kelas, konstruktor, atau pengikatan objek (tapi itu tidak masuk akal).



//   
function createHuman (...args) {
  return new Human(...args)
}


Tempat mendefinisikan properti dan metode



Di mana Anda harus mendefinisikan properti dan metode? Contoh atau prototipe?



Banyak orang berpikir bahwa prototipe lebih baik untuk ini.



Namun, itu tidak terlalu penting.



Dengan mendefinisikan properti dan metode pada sebuah instance, setiap instance akan menggunakan lebih banyak memori. Saat menentukan metode dalam prototipe, memori akan dikonsumsi lebih sedikit, tetapi tidak signifikan. Mengingat kekuatan komputer modern, perbedaan ini tidak signifikan. Jadi lakukan apa pun yang terbaik untuk Anda, tetapi tetap lebih suka prototipe.



Misalnya, saat menggunakan class atau object binding, lebih baik menggunakan prototipe karena membuat kode lebih mudah untuk ditulis. Dalam kasus FF, prototipe tidak dapat digunakan. Hanya properti instance yang dapat ditentukan.



Approx. per.: izinkan saya tidak setuju dengan penulis. Masalah menggunakan prototipe dan bukan contoh ketika mendefinisikan properti dan metode tidak hanya masalah konsumsi memori, tetapi di atas semua itu masalah tujuan properti atau metode yang sedang didefinisikan. Jika properti atau metode harus unik untuk setiap instance, maka itu harus ditentukan pada instance. Jika properti atau metode harus sama (umum) untuk semua instance, maka itu harus ditentukan dalam prototipe. Dalam kasus terakhir, jika Anda perlu membuat perubahan pada properti atau metode, itu akan cukup untuk membuatnya menjadi prototipe, berbeda dengan properti dan metode instance, yang disesuaikan secara individual.



Kesimpulan awal



Berdasarkan materi yang dipelajari, dapat ditarik beberapa kesimpulan. Itu pendapat pribadi saya.



  • Kelas lebih baik daripada konstruktor karena membuatnya lebih mudah untuk mendefinisikan banyak metode.
  • Pengikatan objek tampak aneh karena kebutuhan untuk menggunakan Object.create. Saya terus melupakan hal ini ketika mempelajari pendekatan ini. Bagi saya, ini adalah alasan yang cukup untuk menolak penggunaan lebih lanjut.
  • Kelas dan FF adalah yang paling mudah digunakan. Masalahnya adalah prototipe tidak dapat digunakan di FF. Tapi, seperti yang saya catat sebelumnya, itu tidak terlalu penting.


Selanjutnya, kita akan membandingkan kelas dan FF sebagai dua cara terbaik untuk membuat objek di JavaScript.



Kelas vs. FF - Warisan



Sebelum melanjutkan ke membandingkan kelas dan FF, Anda perlu berkenalan dengan tiga konsep yang mendasari OOP:



  • warisan
  • enkapsulasi
  • ini


Mari kita mulai dengan warisan.



Apa itu warisan?


Dalam JavaScript, pewarisan berarti meneruskan properti dari induk ke anak, mis. dari proyek ke contoh.



Ini terjadi dalam dua cara:



  • menggunakan inisialisasi instance
  • menggunakan rantai prototipe


Dalam kasus kedua, proyek induk diperluas dengan proyek anak. Ini disebut subclassing, tetapi beberapa juga menyebutnya warisan.



Memahami Subclassing


Subclassing adalah ketika proyek anak memperluas induknya.



Mari kita lihat contoh kelas.



Subclassing dengan kelas


Kata kunci "extends" digunakan untuk memperluas kelas induk.



class Child extends Parent {
    // ...
}


Sebagai contoh, mari buat kelas Pengembang yang memperluas kelas Manusia.



//  Human
class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}


Kelas "Pengembang" akan memperluas Manusia sebagai berikut:



class Developer extends Human {
  constructor(firstName, lastName) {
    super(firstName, lastName)
  }

    // ...
}


Kata kunci super memanggil konstruktor kelas Manusia. Jika Anda tidak membutuhkan ini, super dapat dihilangkan.



class Developer extends Human {
  // ...
}


Katakanlah Pengembang dapat menulis kode (siapa sangka). Mari tambahkan metode yang sesuai untuk itu.



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


Berikut adalah contoh instance kelas "Pengembang".



const chris = new Developer('Chris', 'Coyier')
console.log(chris)






Subclassing dengan FF


Untuk membuat subclass menggunakan FF, Anda perlu melakukan 4 langkah:



  • buat FF baru
  • membuat instance dari proyek induk
  • buat salinan dari instance ini
  • tambahkan properti dan metode ke salinan ini


Proses ini terlihat seperti ini.



function Subclass (...args) {
  const instance = ParentClass(...args)
  return Object.assign({}, instance, {
    //   
  })
}


Mari buat subkelas "Pengembang". Seperti inilah bentuk FF "Manusia".



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}


Buat Pengembang.



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    //   
  })
}


Tambahkan metode "kode" untuk itu.



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}


Kami membuat contoh Pengembang.



const chris = Developer('Chris', 'Coyier')
console.log(chris)






Menimpa metode induk


Terkadang perlu untuk menimpa metode induk dalam subkelas. Hal ini dapat dilakukan sebagai berikut:



  • buat metode dengan nama yang sama
  • panggil metode induk (opsional)
  • buat metode baru di subclass


Proses ini terlihat seperti ini.



class Developer extends Human {
  sayHello () {
    //   
    super.sayHello()

    //   
    console.log(`I'm a developer.`)
  }
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()






Proses yang sama menggunakan FF.



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)

  return Object.assign({}, human, {
      sayHello () {
        //   
        human.sayHello()

        //   
        console.log(`I'm a developer.`)
      }
  })
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()






Warisan versus komposisi


Pembicaraan tentang warisan jarang terjadi tanpa menyebutkan komposisi. Pakar seperti Eric Elliot percaya bahwa komposisi harus digunakan jika memungkinkan.



Apakah komposisi itu?



Memahami komposisi


Komposisi pada dasarnya adalah gabungan dari beberapa hal menjadi satu. Cara paling umum dan paling sederhana untuk menggabungkan objek menggunakan Object.assign.



const one = { one: 'one' }
const two = { two: 'two' }
const combined = Object.assign({}, one, two)


Komposisi paling mudah dijelaskan dengan sebuah contoh. Katakanlah kita memiliki dua subclass, Pengembang dan Desainer. Desainer tahu cara mendesain, dan pengembang tahu cara menulis kode. Keduanya mewarisi dari kelas "Manusia".



class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class Designer extends Human {
  design (thing) {
    console.log(`${this.firstName} designed ${thing}`)
  }
}

class Developer extends Designer {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


Sekarang, misalkan kita ingin membuat subclass ketiga. Subkelas ini harus merupakan campuran dari perancang dan pengembang - harus dapat merancang dan menulis kode. Sebut saja DesignerDeveloper (atau DeveloperDesigner, jika Anda lebih suka).



Bagaimana cara kami membuatnya?



Kami tidak dapat memperluas kelas "Desainer" dan "Pengembang" pada saat yang bersamaan. Ini tidak mungkin karena kami tidak dapat memutuskan properti mana yang harus didahulukan. Ini yang disebut masalah berlian (diamond inheritance) .







Masalah belah ketupat dapat diselesaikan dengan Object.assign jika kita memberikan prioritas satu objek di atas yang lain. Namun, JavaScript tidak mendukung pewarisan ganda.



//  
class DesignerDeveloper extends Developer, Designer {
  // ...
}


Di sinilah komposisi menjadi berguna.



Pendekatan ini menyatakan berikut ini: alih-alih membuat subkelas DesignerDeveloper, buatlah objek yang berisi keterampilan yang dapat Anda subkelas sesuai kebutuhan.



Penerapan pendekatan ini mengarah pada hal berikut.



const skills = {
    code (thing) { /* ... */ },
    design (thing) { /* ... */ },
    sayHello () { /* ... */ }
}


Kita tidak membutuhkan kelas Manusia lagi, karena kita dapat membuat tiga kelas yang berbeda menggunakan objek yang ditentukan.



Berikut adalah kode untuk DesignerDeveloper.



class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

const chris = new DesignerDeveloper('Chris', 'Coyier')
console.log(chris)






Kami dapat melakukan hal yang sama untuk Desainer dan Pengembang.



class Designer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

class Developer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      sayHello: skills.sayHello
    })
  }
}


Pernahkah Anda memperhatikan bahwa kami membuat metode pada sebuah instance? Ini hanyalah salah satu opsi yang memungkinkan. Kita juga dapat menempatkan metode dalam prototipe, tetapi menurut saya itu tidak perlu (pendekatan ini sepertinya kita kembali ke konstruktor).



class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design,
  sayHello: skills.sayHello
})






Gunakan pendekatan mana pun yang Anda rasa cocok. Hasilnya akan sama.



Komposisi dengan FF


Komposisi dengan FF adalah tentang menambahkan metode terdistribusi ke objek yang dikembalikan.



function DesignerDeveloper (firstName, lastName) {
  return {
    firstName,
    lastName,
    code: skills.code,
    design: skills.design,
    sayHello: skills.sayHello
  }
}






Warisan dan komposisi


Tidak ada yang mengatakan bahwa kami tidak dapat menggunakan pewarisan dan komposisi pada saat yang bersamaan.



Kembali ke contoh Designer, Developer, dan DesignerDeveloper, perlu dicatat bahwa mereka juga manusia. Oleh karena itu, mereka dapat memperluas kelas Manusia.



Berikut adalah contoh pewarisan dan komposisi menggunakan sintaks kelas.



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class DesignerDeveloper extends Human {}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design
})






Dan disini sama saja dengan penggunaan FF.



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

function DesignerDeveloper (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code: skills.code,
    design: skills.design
  })
}






Subclass di dunia nyata


Sementara banyak ahli berpendapat bahwa komposisi lebih fleksibel (dan karena itu lebih berguna) daripada subclass, subclass tidak boleh diabaikan. Banyak hal yang kami tangani didasarkan pada strategi ini.



Misalnya: acara "click" adalah MouseEvent. MouseEvent adalah subkelas dari UIEvent (acara antarmuka pengguna), yang pada gilirannya merupakan subkelas Acara (acara).







Contoh lain: Elemen HTML adalah subclass dari Node. Oleh karena itu, mereka dapat menggunakan semua properti dan metode node.







Kesimpulan awal tentang warisan


Pewarisan dan komposisi dapat digunakan di kedua kelas dan FF. Di FF, komposisinya terlihat "lebih bersih", tetapi ini adalah sedikit keunggulan dibandingkan kelas.



Mari lanjutkan perbandingannya.



Kelas vs. FF - Enkapsulasi



Pada dasarnya, enkapsulasi adalah tentang menyembunyikan satu hal di dalam yang lain, membuat esensi batin tidak dapat diakses dari luar.



Di JavaScript, entitas tersembunyi adalah variabel dan fungsi yang hanya tersedia dalam konteks saat ini. Dalam hal ini, konteksnya sama dengan ruang lingkup.



Enkapsulasi sederhana


Bentuk enkapsulasi yang paling sederhana adalah blok kode.



{
  // ,  ,     
}


Saat berada di dalam blok, Anda dapat mengakses variabel yang dideklarasikan di luarnya.



const food = 'Hamburger'

{
  console.log(food)
}






Tapi tidak sebaliknya.



{
  const food = 'Hamburger'
}

console.log(food)






Perhatikan bahwa variabel yang dideklarasikan dengan kata kunci "var" memiliki cakupan global atau fungsional. Cobalah untuk tidak menggunakan var untuk mendeklarasikan variabel.



Enkapsulasi dengan suatu fungsi


Lingkup fungsional mirip dengan lingkup blok. Variabel yang dideklarasikan dalam suatu fungsi hanya dapat diakses di dalamnya. Ini berlaku untuk semua variabel, bahkan yang dideklarasikan dengan var.



function sayFood () {
  const food = 'Hamburger'
}

sayFood()
console.log(food)






Saat kita berada di dalam suatu fungsi, kita memiliki akses ke variabel yang dideklarasikan di luarnya.



const food = 'Hamburger'

function sayFood () {
  console.log(food)
}

sayFood()






Fungsi dapat mengembalikan nilai yang nantinya dapat digunakan di luar fungsi.



function sayFood () {
  return 'Hamburger'
}

console.log(sayFood())






Penutupan


Penutupan adalah bentuk enkapsulasi tingkat lanjut. Ini hanya fungsi di dalam fungsi lain.



//  
function outsideFunction () {
  function insideFunction () { /* ... */ }
}




Variabel yang dideklarasikan di outsideFunction bisa digunakan di insideFunction.



function outsideFunction () {
  const food = 'Hamburger'
  console.log('Called outside')

  return function insideFunction () {
    console.log('Called inside')
    console.log(food)
  }
}

//  outsideFunction,   insideFunction
//  insideFunction   "fn"
const fn = outsideFunction()






Enkapsulasi dan OOP


Saat membuat objek, kami ingin beberapa properti menjadi publik (publik) dan yang lainnya privat (privat atau privat).



Mari kita lihat contohnya. Katakanlah kita memiliki proyek Mobil. Saat membuat instance baru, kami menambahkan properti "bahan bakar" padanya dengan nilai 50.



class Car {
  constructor () {
    this.fuel = 50
  }
}




Pengguna dapat menggunakan properti ini untuk menentukan jumlah bahan bakar yang tersisa.



const car = new Car()
console.log(car.fuel) // 50




Pengguna juga bisa mengatur sendiri jumlah bahan bakarnya.



const car = new Car()
car.fuel = 3000
console.log(car.fuel) // 3000


Mari tambahkan kondisi tangki mobil menampung bahan bakar maksimal 100 liter. Kami tidak ingin pengguna dapat menyetel sendiri jumlah bahan bakar, karena dapat merusak mobil.



Ada dua cara untuk melakukan ini:



  • penggunaan properti pribadi berdasarkan konvensi
  • menggunakan bidang pribadi yang nyata


Properti pribadi berdasarkan kesepakatan


Di JavaScript, variabel dan properti pribadi biasanya dilambangkan dengan garis bawah.



class Car {
  constructor () {
    //   "fuel"  ,       
    this._fuel = 50
  }
}


Biasanya, kami membuat metode untuk mengelola properti pribadi.



class Car {
  constructor () {
    this._fuel = 50
  }

  getFuel () {
    return this._fuel
  }

  setFuel (value) {
    this._fuel = value
    //   
    if (value > 100) this._fuel = 100
  }
}


Pengguna harus menggunakan metode getFuel dan setFuel untuk menentukan dan mengatur jumlah bahan bakar.



const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100


Tetapi variabel "_fuel" tidak benar-benar pribadi. Dapat diakses dari luar.



const car = new Car()
console.log(car.getFuel()) // 50

car._fuel = 3000
console.log(car.getFuel()) // 3000


Gunakan bidang pribadi nyata untuk membatasi akses ke variabel.



Bidang yang benar-benar pribadi


Fields adalah istilah yang digunakan untuk menggabungkan variabel, properti, dan metode.



Bidang kelas privat


Kelas memungkinkan Anda membuat variabel privat menggunakan awalan "#".



class Car {
  constructor () {
    this.#fuel = 50
  }
}


Sayangnya, awalan ini tidak dapat digunakan di konstruktor.







Variabel privat harus ditentukan di luar konstruktor.



class Car {
  //   
  #fuel
  constructor () {
    //  
    this.#fuel = 50
  }
}


Dalam hal ini, kita dapat menginisialisasi variabel saat ditentukan.



class Car {
  #fuel = 50
}


Sekarang variabel "#fuel" hanya tersedia di dalam kelas. Mencoba mengaksesnya di luar kelas akan menyebabkan kesalahan.



const car = new Car()
console.log(car.#fuel)






Kami membutuhkan metode yang tepat untuk memanipulasi variabel.



class Car {
  #fuel = 50

  getFuel () {
    return this.#fuel
  }

  setFuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100


Saya pribadi lebih suka menggunakan getter dan setter untuk ini. Menurut saya sintaks ini lebih mudah dibaca.



class Car {
  #fuel = 50

  get fuel () {
    return this.#fuel
  }

  set fuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100


Bidang Pribadi FF


FF membuat bidang pribadi secara otomatis. Kami hanya perlu mendeklarasikan variabel. Pengguna tidak akan dapat mengakses variabel ini dari luar. Ini karena fakta bahwa variabel memiliki ruang lingkup blok (atau fungsional), yaitu dienkapsulasi secara default.



function Car () {
  const fuel = 50
}

const car = new Car()
console.log(car.fuel) // undefined
console.log(fuel) // Error: "fuel" is not defined


Getter dan setter juga digunakan untuk mengontrol variabel privat "bahan bakar".



function Car () {
  const fuel = 50

  return {
    get fuel () {
      return fuel
    },

    set fuel (value) {
      fuel = value
      if (value > 100) fuel = 100
    }
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100


Seperti ini. Sederhana dan mudah!



Kesimpulan awal tentang enkapsulasi


Enkapsulasi FF lebih sederhana dan lebih mudah dipahami. Ini didasarkan pada cakupan, yang merupakan bagian penting dari JavaScript.



Enkapsulasi kelas melibatkan penggunaan awalan "#", yang bisa agak membosankan.



Kelas melawan FF - ini



ini adalah argumen utama yang menentang penggunaan kelas. Mengapa? Karena arti ini tergantung di mana dan bagaimana ini digunakan. Perilaku ini seringkali membingungkan tidak hanya untuk pemula, tetapi juga untuk developer berpengalaman.



Namun, konsep ini sebenarnya tidak terlalu sulit. Ada total 6 konteks di mana ini dapat digunakan. Jika Anda ahli dengan konteks ini, Anda seharusnya tidak memiliki masalah dengan ini.



Konteks bernama adalah:



  • konteks global
  • konteks objek yang sedang dibuat
  • konteks properti atau metode suatu objek
  • fungsi sederhana
  • fungsi panah
  • konteks penanganan acara


Tapi kembali ke artikelnya. Mari kita lihat secara spesifik menggunakan ini di kelas dan FF.



Menggunakan ini di kelas


Saat digunakan di kelas, ini menunjuk ke instance yang sedang dibuat (konteks properti / metode). Inilah mengapa instance diinisialisasi dalam konstruktor.



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    console.log(this)
  }
}

const chris = new Human('Chris', 'Coyier')






Menggunakan ini dalam fungsi konstruktor


Saat menggunakan ini di dalam fungsi dan new untuk membuat sebuah instance, ini akan mengarah ke instance tersebut.



function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
  console.log(this)
}

const chris = new Human('Chris', 'Coyier')






Berbeda dengan FK di FF ini menunjuk ke jendela (dalam konteks modul, ini umumnya memiliki nilai "tidak ditentukan").



//        "new"
function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
  console.log(this)
}

const chris = Human('Chris', 'Coyier')






Oleh karena itu, ini tidak boleh digunakan di FF. Inilah salah satu perbedaan utama antara FF dan FC.



Menggunakan ini di FF


Untuk dapat menggunakan ini di FF, perlu untuk membuat konteks properti / metode.



function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayThis () {
      console.log(this)
    }
  }
}

const chris = Human('Chris', 'Coyier')
chris.sayThis()






Meskipun kami dapat menggunakan ini di FF, kami tidak membutuhkannya. Kita dapat membuat variabel yang menunjuk ke instance tersebut. Variabel seperti itu dapat digunakan sebagai pengganti ini.



function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${human.firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()


human.firstName lebih akurat daripada this.firstName karena human secara eksplisit menunjuk ke sebuah instance.



Faktanya, kita bahkan tidak perlu menulis human.firstName. Kita dapat membatasi diri kita pada firstName, karena variabel ini memiliki ruang lingkup leksikal (ini adalah saat nilai variabel diambil dari lingkungan luar).



function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()






Mari kita lihat contoh yang lebih kompleks.



Contoh yang kompleks



Syaratnya adalah sebagai berikut: kita memiliki proyek "Manusia" dengan properti "firstName" dan "lastName" dan metode "sayHello".



Kami juga memiliki proyek "Pengembang" yang diwarisi dari Manusia. Pengembang tahu cara menulis kode, jadi mereka harus memiliki metode "kode". Selain itu, mereka harus menyatakan bahwa mereka berada dalam kasta pengembang, jadi kita perlu menimpa metode sayHello.



Mari menerapkan logika yang ditentukan menggunakan kelas dan FF.



Kelas


Kami membuat proyek "Manusia".



class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}


Buat proyek "Pengembang" dengan metode "kode".



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}


Kami menimpa metode "sayHello".



class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }

  sayHello () {
    super.sayHello()
    console.log(`I'm a developer`)
  }
}


FF (menggunakan ini)


Kami membuat proyek "Manusia".



function Human () {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}


Buat proyek "Pengembang" dengan metode "kode".



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}


Kami menimpa metode "sayHello".



function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    },

    sayHello () {
      human.sayHello()
      console.log('I\'m a developer')
    }
  })
}


Ff (tanpa ini)


Karena firstName secara leksikal dicakup secara langsung, kita bisa menghilangkan ini.



function Human (firstName, lastName) {
  return {
    // ...
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

function Developer (firstName, lastName) {
  // ...
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${firstName} coded ${thing}`)
    },

    sayHello () { /* ... */ }
  })
}


Kesimpulan awal tentang ini


Dengan kata sederhana, kelas membutuhkan penggunaan ini, tetapi FF tidak. Dalam hal ini, saya lebih suka menggunakan FF karena:



  • konteks ini bisa berubah
  • kode yang ditulis menggunakan FF lebih pendek dan lebih bersih (juga karena enkapsulasi variabel otomatis)


Kelas vs. FF - Penangan Acara



Banyak artikel di OOP mengabaikan fakta bahwa sebagai frontend developer, kami terus-menerus berurusan dengan event handler. Mereka menyediakan interaksi dengan pengguna.



Karena penangan kejadian mengubah konteks ini, bekerja dengannya di kelas bisa menjadi masalah. Pada saat yang sama, masalah seperti itu tidak muncul di FF.



Namun, mengubah konteks ini tidak masalah jika kita tahu cara menanganinya. Mari kita lihat contoh sederhana.



Buat penghitung


Untuk membuat penghitung, kita akan menggunakan pengetahuan yang didapat, termasuk variabel privat.



Penghitung kami akan berisi dua hal:



  • konter itu sendiri
  • tombol untuk meningkatkan nilainya






Seperti inilah tampilan markup:



<div class="counter">
  <p>Count: <span>0</span></p>
  <button>Increase Count</button>
</div>


Membuat penghitung menggunakan kelas


Untuk mempermudah, minta pengguna untuk menemukan dan meneruskan markup penghitung ke kelas Penghitung:



class Counter {
  constructor (counter) {
    // ...
  }
}

// 
const counter = new Counter(document.querySelector('.counter'))


Anda perlu mendapatkan 2 elemen di kelas:



  • <span> berisi nilai penghitung - kita perlu memperbarui nilai ini ketika penghitung meningkat
  • <button> - kita perlu menambahkan penangan untuk kejadian yang dipanggil oleh elemen ini


class Counter {
  constructor (counter) {
    this.countElement = counter.querySelector('span')
    this.buttonElement = counter.querySelector('button')
  }
}


Selanjutnya, kami menginisialisasi variabel "count" dengan konten teks countElement. Variabel yang ditentukan harus pribadi.



class Counter {
  #count
  constructor (counter) {
    // ...

    this.#count = parseInt(countElement.textContent)
  }
}


Ketika tombol ditekan, nilai penghitung harus meningkat 1. Kami menerapkan ini menggunakan metode "peningkatanCount".



class Counter {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
  }
}


Sekarang kita perlu memperbarui DOM. Mari kita terapkan ini menggunakan metode "updateCount" yang disebut di dalam peningkatanCount:



class Counter {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
    this.updateCount()
  }

  updateCount () {
    this.countElement.textContent = this.#count
  }
}


Tetap menambahkan event handler.



Menambahkan event handler


Mari tambahkan handler ke this.buttonElement. Sayangnya, kami tidak dapat menggunakan peningkatanCount sebagai fungsi panggilan balik. Ini akan menyebabkan kesalahan.



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  // 
}






Pengecualian dilempar karena ini menunjuk ke buttonElement (konteks penanganan peristiwa). Anda dapat memverifikasi ini dengan mencetak nilai ini ke konsol.







Nilai ini harus diubah agar mengarah ke instance. Ini dapat dilakukan dengan dua cara:



  • menggunakan bind
  • menggunakan fungsi panah


Kebanyakan menggunakan metode pertama (tetapi yang kedua lebih sederhana).



Menambahkan event handler dengan bind


bind mengembalikan fungsi baru. Sebagai argumen pertama, ia melewati objek yang akan dituju (ke mana ini akan diikat).



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount.bind(this))
  }

  // ...
}


Berhasil, tetapi tidak terlihat bagus. Selain itu, bind adalah fitur lanjutan yang sulit ditangani oleh pemula.



Fungsi panah


Fungsi panah ini antara lain tidak punya sendiri-sendiri ini. Mereka meminjamnya dari lingkungan leksikal (eksternal). Oleh karena itu, kode penghitung dapat ditulis ulang sebagai berikut:



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', () => {
      this.increaseCount()
    })
  }

  // 
}


Ada cara yang lebih mudah. Kita dapat membuat peningkatanCount sebagai fungsi panah. Dalam kasus ini, ini akan mengarah ke instance.



class Counter {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  increaseCount = () => {
    this.#count = this.#count + 1
    this.updateCounter()
  }

  // ...
}


Kode


Berikut ini contoh kode lengkapnya:







Membuat penghitung menggunakan FF


Awalnya serupa - kami meminta pengguna untuk menemukan dan meneruskan markup penghitung:



function Counter (counter) {
  // ...
}

const counter = Counter(document.querySelector('.counter'))


Kami mendapatkan elemen yang diperlukan, yang akan menjadi pribadi secara default:



function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')
}


Mari kita inisialisasi variabel "count":



function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')

  let count = parseInt(countElement.textContext)
}


Nilai penghitung akan dinaikkan menggunakan metode "peningkatanCount". Anda dapat menggunakan fungsi biasa, tetapi saya lebih suka pendekatan yang berbeda:



function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
    }
  }
}


DOM akan diperbarui menggunakan metode "updateCount" yang dipanggil di dalam peningkatanCount:



function Counter (counter) {
  // ...
  const counter = {
    increaseCount () {
      count = count + 1
      counter.updateCount()
    },

    updateCount () {
      increaseCount()
    }
  }
}


Perhatikan bahwa kami menggunakan counter.updateCount daripada this.updateCount.



Menambahkan event handler


Kita bisa menambahkan event handler ke buttonElement menggunakan counter.increaseCount sebagai callback.



Ini akan bekerja karena kita tidak menggunakan ini, jadi tidak masalah bagi kita bahwa penangan mengubah konteks ini.



function Counter (counterElement) {
  // 

  // 
  const counter = { /* ... */ }

  //  
  buttonElement.addEventListener('click', counter.increaseCount)
}


Fitur pertama ini


Anda dapat menggunakan ini di FF, tetapi hanya dalam konteks metode.



Dalam contoh berikut, memanggil counter.increaseCount akan memanggil counter.updateCount karena ini mengarah ke counter:



function Counter (counterElement) {
  // 

  // 
  const counter = {
    increaseCount() {
      count = count + 1
      this.updateCount()
    }
  }

  //  
  buttonElement.addEventListener('click', counter.increaseCount)
}


Namun, pengendali kejadian tidak akan berfungsi karena nilai ini telah berubah. Masalah ini bisa diatasi dengan mengikat, tetapi tidak dengan fungsi panah.



Fitur kedua ini


Saat menggunakan sintaks FF, kita tidak dapat membuat metode dalam bentuk fungsi panah, karena metode dibuat dalam konteks suatu fungsi, yaitu. ini akan mengarah ke jendela:



function Counter (counterElement) {
  // ...
  const counter = {
    //   
    //  ,  this   window
    increaseCount: () => {
      count = count + 1
      this.updateCount()
    }
  }
  // ...
}


Oleh karena itu, saat menggunakan FF, saya sangat menyarankan untuk menghindari penggunaan ini.



Kode








Putusan penangan acara


Penangan acara mengubah nilai ini, jadi gunakan ini dengan sangat hati-hati. Saat menggunakan kelas, saya menyarankan Anda untuk membuat callback event handler dalam bentuk fungsi panah. Maka Anda tidak perlu menggunakan layanan bind.



Saat menggunakan FF, saya sarankan untuk melakukannya tanpa ini sama sekali.



Kesimpulan



Jadi, dalam artikel ini, kami melihat empat cara membuat objek di JavaScript:



  • Fungsi konstruktor
  • Kelas
  • Menghubungkan objek
  • Fungsi pabrik


Pertama, kami sampai pada kesimpulan bahwa class dan FF adalah cara paling optimal untuk membuat objek.



Kedua, kami melihat bahwa subclass lebih mudah dibuat dengan class. Namun, untuk komposisi, lebih baik menggunakan FF.



Ketiga, kami meringkas bahwa dalam hal enkapsulasi, FF memiliki keunggulan dibandingkan kelas, karena yang terakhir memerlukan penggunaan awalan "#" khusus, dan FF membuat variabel privat secara otomatis.



Keempat, FF memungkinkan Anda melakukan tanpa menggunakan ini sebagai referensi instans. Di kelas, Anda harus menggunakan beberapa trik untuk mengembalikan ini ke konteks asli yang diubah oleh event handler.



Itu semua untukku. Saya harap Anda menikmati artikelnya. Terima kasih atas perhatian Anda.



All Articles