Definisi Kelas atau Objek EkmaScript

Menggunakan objek yang diatur sebelumnya hanya bagian dari kemampuan bahasa berorientasi objek, kekuatan sebenarnya adalah kemampuan untuk membuat kelas dan objek khusus sendiri.

ECMAScript memiliki banyak metode untuk membuat objek atau kelas.

Cara pabrik

Cara asli

Karena properti objek dapat ditentukan dinamis setelah objek dibuat, banyak pengembang menulis kode seperti yang berikut saat JavaScript diperkenalkan pertama kali:

var oCar = new Object;
oCar.color = "blue";
oCar.doors = 4;
oCar.mpg = 25;
oCar.showColor = function() {
  alert(this.color);
};

Try It Yourself (TIY)

Dalam kode di atas, objek car dibuat. Lalu, beberapa properti disetel: warna adalah biru, memiliki empat pintu, dan dapat berjalan 25 mil per galon. Properti terakhir sebenarnya adalah penunjuk ke fungsi, yang berarti properti ini adalah metode. Setelah menjalankan kode ini, objek car dapat digunakan.

Namun disini ada masalah, yaitu mungkin perlu untuk membuat beberapa instansi car.

Solusi: Cara Pabrik

Untuk menyelesaikan masalah ini, para pengembang menciptakan fungsi pabrik yang dapat membuat dan mengembalikan objek tipe khusus.

Contohnya, fungsi createCar() dapat digunakan untuk mengembungkan operasi pengembangan objek car yang dijelaskan sebelumnya:

function createCar() {
  var oTempCar = new Object;
  oTempCar.color = "blue";
  oTempCar.doors = 4;
  oTempCar.mpg = 25;
  oTempCar.showColor = function() {
    alert(this.color);
  };
  return oTempCar;
}
var oCar1 = createCar();
var oCar2 = createCar();

Try It Yourself (TIY)

Di sini, semua kode dalam contoh pertama termasuk di dalam fungsi createCar(). Selain itu, ada satu baris kode ekstra, yang mengembalikan objek car (oTempCar) sebagai nilai fungsi. Menempuh fungsi ini, akan membuat objek baru dan memberikannya semua properti yang diperlukan, menyalin objek car yang dijelaskan sebelumnya. Dengan cara ini, kami dapat dengan mudah membuat dua versi objek car (oCar1 dan oCar2), yang propertinya sama sekali.

Mengirimkan parameter ke fungsi

Kami juga dapat memodifikasi fungsi createCar(), untuk memberikan nilai default bagi setiap properti, bukannya hanya memberikan nilai default properti:

function createCar(sColor,iDoors,iMpg) {
  var oTempCar = new Object;
  oTempCar.color = sColor;
  oTempCar.doors = iDoors;
  oTempCar.mpg = iMpg;
  oTempCar.showColor = function() {
    alert(this.color);
  };
  return oTempCar;
}
var oCar1 = createCar("red",4,23);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();		//keluar "red"
oCar2.showColor();		//keluar "blue"

Try It Yourself (TIY)

Dengan menambahkan parameter ke fungsi createCar(), dapat memberikan nilai properti color, doors, dan mpg objek car yang akan dibuat. Ini membuat dua objek yang memiliki properti yang sama, tetapi nilai properti yang berbeda.

Mengdefinikan metode objek di luar fungsi pabrik

Walaupun ECMAScript semakin resmi, tetapi cara untuk membuat objek diabaikan, dan standarisasi ini masih diserang hingga kini. Bagian besar disebabkan oleh alasan semantik (kelihatannya seperti belum resmi seperti yang digunakan dengan operator konstruktur new), dan bagian lain disebabkan oleh alasan fungsional. Alasan fungsional berada di tempat dimana harus membuat objek dengan cara ini. Dalam contoh sebelumnya, setiap kali dipanggil fungsi createCar(), harus membuat fungsi baru showColor(), yang berarti setiap objek memiliki versi showColor() yang sendiri. Namun, kenyataannya, setiap objek membagi fungsi yang sama.

Beberapa pengembang mendefinisikan method objek di luar fungsi pabrik, kemudian melalui atribut menunjuk ke method itu, untuk menghindari masalah ini:}

function showColor() {
  alert(this.color);
}
function createCar(sColor,iDoors,iMpg) {
  var oTempCar = new Object;
  oTempCar.color = sColor;
  oTempCar.doors = iDoors;
  oTempCar.mpg = iMpg;
  oTempCar.showColor = showColor;
  return oTempCar;
}
var oCar1 = createCar("red",4,23);
var oCar2 = createCar("blue",3,25);
oCar1.showColor();		//keluar "red"
oCar2.showColor();		//keluar "blue"

Try It Yourself (TIY)

Dalam kode yang diubah ulang di atas, fungsi showColor() didefinisikan sebelum fungsi createCar(). Dalam createCar(), diberikan penunjukan objek yang mengarah ke fungsi showColor() yang sudah ada. Secara fungsional, hal ini mengatasi masalah pembuatan ulang objek fungsi; tetapi secara semantik, fungsi ini kurang seperti method objek.

semua masalah ini yang memicudidefinisikan pengembangmunculnya konstruktur fungsi.

Cara konstruktur fungsi

Membuat konstruktur fungsi sama mudah seperti membuat fungsi pabrik. Langkah pertama adalah memilih nama kelas, yaitu nama fungsi konstruktur. Menurut konvensi, huruf pertama nama ini dijadikan huruf besar untuk membedakannya dengan nama variabel yang biasanya huruf kecil. Selain ini, fungsi konstruktur mirip dengan fungsi pabrik. Dengan demikian, berikut adalah contoh:

function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.showColor = function() {
    alert(this.color);
  };
}
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);

Try It Yourself (TIY)

Di sini kami menjelaskan perbedaan kode di atas dengan cara pabrik. Pada awalnya, di dalam konstruktur fungsi tidak ada objek yang dibuat, melainkan menggunakan kata kunci this. Saat menggunakan operator new untuk memanggil fungsi konstruktur, sebelum baris pertama dijalankan, terlebih dahulu objek dibuat, hanya dengan this yang dapat mengakses objek itu. kemudian langsung diberikan nilai atribut this, secara baku adalah nilai pengembalian fungsi konstruktur (tidak perlu menggunakan operator return secara eksplisit).

Sekarang, penggunaan operator new dan nama kelas Car untuk membuat objek lebih seperti cara pembuatan objek umum dalam ECMAScript.

Anda mungkin akan bertanya, apakah masalah manajemen fungsi ini ada di metode yang pertama? Ya.

Seperti fungsi pabrik, fungsi konstruktur akan menghasilkan ulang fungsi, menciptakan versi fungsi yang terpisah untuk setiap objek. Meskipun mirip dengan fungsi pabrik, fungsi konstruktur juga dapat ditulis ulang dengan fungsi eksternal, tetapi secara semantik ini tidak ada arti. Ini adalah keunggulan cara prototipe yang akan disebutkan selanjutnya.

Cara prototipe

Cara prototipe menggunakan atribut prototype objek, dapat dianggap seperti prototipe yang dipergunakan untuk membuat objek baru.

Di sini, pertama-tama digunakan konstruktur kosong untuk menetapkan nama kelas. Lalu semua atribut dan metode diberikan langsung ke atribut prototype. Kita mengulangi contoh sebelumnya, kode seperti berikut:

function Car() {
}
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
  alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();

Try It Yourself (TIY)

Dalam kode ini, pertama-tama didefinisikan fungsi konstruktur (Car), tanpa ada kode. Berikutnya, beberapa baris kode menambahkan atribut ke atribut prototype untuk mendefinisikan atribut objek Car. Pada saat pemanggilan new Car(), semua atribut prototype segera diberikan ke objek yang akan dibuat, yang berarti semua instansia Car menempatkan pointer ke fungsi showColor(). Secara semantik, semua atribut terlihat seperti milik objek, sehingga memecahkan masalah yang ada di metode dua yang pertama.

Selain itu, dengan cara ini, Anda dapat menggunakan operator instanceof untuk memeriksa tipe objek yang ditunjuk variabel. Oleh karena itu, kode di bawah ini akan keluar TRUE:

alert(oCar1 instanceof Car); // keluaran "true"

Masalah cara prototipe

Cara prototipe terlihat seperti solusi yang bagus. Sayangnya, ini belum memuaskan.

Pertama-tama, fungsi konstruktur ini tidak memiliki parameter. Dengan cara prototipe, nilai atribut yang diinisialisasi tidak dapat disampaikan melalui pengiriman parameter ke fungsi konstruktur, karena atribut color untuk Car1 dan Car2 semua sama dengan "blue", atribut doors semua sama dengan 4, dan atribut mpg semua sama dengan 25. Ini berarti atribut nilai standar harus diubah setelah objek dibuat, hal ini sangat mengecewakan, tetapi belum selesai. Masalah yang sebenarnya muncul saat atribut menunjuk ke objek, bukannya fungsi. Perkalian fungsi tidak akan menyebabkan masalah, tetapi objek jarang dipartisi antara beberapa instansia. Berpikir tentang contoh di bawah ini:

function Car() {
}
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.drivers = new Array("Mike","John");
Car.prototype.showColor = function() {
  alert(this.color);
};
var oCar1 = new Car();
var oCar2 = new Car();
oCar1.drivers.push("Bill");
alert(oCar1.drivers); // Output "Mike,John,Bill"
alert(oCar2.drivers); // Output "Mike,John,Bill"

Try It Yourself (TIY)

Dalam kode di atas, atribut drivers adalah penunjuk ke objek Array, yang mengandung dua nama "Mike" dan "John". Karena drivers adalah nilai referensi, dua instansi Car mengarah ke array yang sama. Ini berarti menambahkan nilai "Bill" ke oCar1.drivers, dapat dilihat di oCar2.drivers. Menampilkan salah satu penunjuk ini, hasilnya adalah menampilkan string "Mike,John,Bill".

Karena ada banyak pertanyaan tentang pengembangan objek, Anda pasti merasa, apakah ada cara yang masuk akal untuk membuat objek? Jawabnya ada, perlu menggabungkan konstraktor dan cara prototipe.

Cara campuran konstraktor/prototipe

Dengan menggabungkan konstraktor dan cara prototipe, dapat menciptakan objek seperti dalam bahasa pemrograman lain. Konsep ini sangat sederhana, yaitu dengan menggunakan konstraktor untuk mendefinisikan semua atribut bukan fungsi objek, dan menggunakan cara prototipe untuk mendefinisikan atribut fungsi (metode) objek. Akibatnya, semua fungsi hanya dibuat sekali, dan setiap objek memiliki instansi atribut objeknya sendiri.

Kami mengulang contoh sebelumnya, kode seperti berikut:

function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.drivers = new Array("Mike","John");
}
Car.prototype.showColor = function() {
  alert(this.color);
};
var oCar1 = new Car("red",4,23);
var oCar2 = new Car("blue",3,25);
oCar1.drivers.push("Bill");
alert(oCar1.drivers); // Output "Mike,John,Bill"
alert(oCar2.drivers); // Output "Mike,John"

Try It Yourself (TIY)

Sekarang ini lebih seperti membuat objek biasa. Semua atribut bukan fungsi dibuat di konstraktor, berarti dapat memberikan nilai default untuk atribut melalui parameter konstraktor. Karena hanya membuat satu instansi fungsi showColor(), jadi tidak ada pengeluaran memori yang dihabiskan. Selain itu, menambahkan nilai "Bill" ke array drivers oCar1, tidak akan mempengaruhi array oCar2, jadi saat menampilkan nilai array ini, oCar1.drivers menampilkan "Mike,John,Bill", sementara oCar2.drivers menampilkan "Mike,John". Karena menggunakan cara prototipe, masih dapat menggunakan operator instanceof untuk mengecek tipe objek.

Cara ini adalah cara utama yang digunakan ECMAScript, ia memiliki sifat dari cara lainnya, tetapi tanpa efek samping mereka. Namun, beberapa pengembang masih merasa metode ini kurang sempurna.

Metode Prototipe Dinamis

Bagi pengembang yang berbiasa menggunakan bahasa lain, menggunakan cara campuran konstruktur/fungsi prototipe mungkin kurang harmonis. Bahkan, saat mendefinisikan kelas, sebagian besar bahasa pemrograman orientasi objek mengelola atribut dan metode secara visual.

class Car {
  public String color = "blue";
  public int doors = 4;
  public int mpg = 25;
  public Car(String color, int doors, int mpg) {
    this.color = color;
    this.doors = doors;
    this.mpg = mpg;
  }
  public void showColor() {
    System.out.println(color);
  }
}

Java mengepack class Car semua atribut dan metode dengan baik, sehingga melihat kode ini kita tahu apa yang akan diwujudkan, ia mendefinisikan informasi objek.

Ideanya dasar metode prototipe dinamis sama seperti cara campuran konstruktur/fungsi prototipe, yaitu mendefinisikan atribut bukan fungsi di dalam konstruktur, sementara atribut fungsi menggunakan atribut prototipe untuk definisi. Perbedaan utamanya adalah posisi yang diberikan kepada metode objek. Berikut adalah kelas Car yang ditulis ulang menggunakan metode prototipe dinamis:

function Car(sColor,iDoors,iMpg) {
  this.color = sColor;
  this.doors = iDoors;
  this.mpg = iMpg;
  this.drivers = new Array("Mike","John");
  if (typeof Car._initialized == "undefined") {
    Car.prototype.showColor = function() {
      alert(this.color);
    };
    Car._initialized = true;
  }
}

Try It Yourself (TIY)

Konstruktur ini belum berubah sampai dicek apakah typeof Car._initialized sama dengan "undefined". Ini adalah bagian paling penting di metode primitif yang dinamis. Jika nilai ini belum ditentukan, konstruktur akan melanjutkan mendefinisikan metode objek dengan metode prototipe, dan mengatur Car._initialized menjadi true. Jika nilai ini sudah ditentukan (nilai true saat typeof adalah Boolean), maka metode ini tidak akan dibuat lagi. Dalam arti umum, metode ini menggunakan tanda ( _initialized) untuk mengecek apakah metode sudah diberikan ke prototipe. Metode ini hanya dibuat dan diatur sekali, para pengembang OOP tradisional akan senang melihat bahwa kode ini seperti definisi kelas di bahasa lain.

Metode factory campuran

Metode ini biasanya digunakan sebagai alternatif saat metode sebelumnya tidak dapat digunakan. Tujuannya adalah untuk membuat konstruktur palsu, hanya untuk mengembalikan instansi objek lain.

Kode ini terlihat seperti factory function:

function Car() {
  var oTempCar = new Object;
  oTempCar.color = "blue";
  oTempCar.doors = 4;
  oTempCar.mpg = 25;
  oTempCar.showColor = function() {
    alert(this.color);
  };
  return oTempCar;
}

Try It Yourself (TIY)

Beberapa hal yang berbeda dengan metode klasik adalah bahwa metode ini menggunakan operator new, sehingga tampak seperti konstruktur yang sebenarnya:

var car = new Car();

Karena operator new dipanggil di dalam konstruktur Car(), operator new kedua (yang berada di luar konstruktur) akan diabaikan, dan objek yang dibuat di dalam konstruktur akan dihubungkan kembali ke variabel car.

Metode ini memiliki masalah yang sama dalam manajemen internal metode objek seperti metode klasik. Disarankan kuat: kecuali untuk keperluan yang wajib, hindari menggunakan metode ini.

Metode yang dipilih

Seperti yang disebutkan sebelumnya, yang paling banyak digunakan saat ini adalah metode konstruktur/prototipe campuran. Selain itu, metode primitif yang dinamis juga populer, yang secara fungsi setara dengan metode konstruktur/prototipe. Dapat dipilih salah satu dari kedua metode ini. Namun, jangan gunakan metode konstruktur atau prototipe klasik secara terpisah, karena hal ini akan memasukkan masalah ke dalam kode.

Contoh

Salah satu hal menarik tentang objek adalah cara mereka memecahkan masalah. Salah satu masalah yang paling sering muncul di ECMAScript adalah kinerja penggabungan string. Seperti bahasa lainnya, string ECMAScript adalah tak dapat diubah, artinya nilai mereka tidak dapat berubah. Berikut adalah contoh kode:

var str = "hello ";
str += "world";

Faktanya, langkah yang dijalankan dibalik kode ini adalah seperti berikut:

  1. Buat string yang akan digunakan untuk menyimpan "hello ".
  2. Buat string yang akan digunakan untuk menyimpan "world".
  3. Buat string yang akan digunakan untuk menyimpan hasil penggabungan.
  4. Salin konten saat ini str ke dalam hasil.
  5. Salin "world" ke dalam hasil.
  6. Perbarui str, supaya mengarah ke hasil.

Setiap kali operasi penggabungan string selesai, langkah 2 sampai 6 akan dijalankan, sehingga operasi ini sangat memakan sumber daya. Jika proses ini diulang beberapa ratus kali, bahkan ribuan kali, akan menyebabkan masalah performa. Solusi adalah menggunakan objek Array untuk menyimpan string, dan kemudian buat string akhir dengan menggunakan metode join() (parameter adalah string kosong). Dengan pikirkan kode berikut untuk menggantikan kode sebelumnya:

var arr = new Array();
arr[0] = "hello ";
arr[1] = "world";
var str = arr.join("");

Dengan demikian, tidak ada masalah jika ada banyak string yang dimasukkan ke dalam array, karena operasi penggabungan hanya terjadi saat memanggil metode join(). Langkah yang dijalankan saat ini adalah sebagai berikut:

  1. Buat string yang akan digunakan untuk menyimpan hasil
  2. Salin setiap string ke posisi yang tepat di dalam hasil

Walaupun solusi ini bagus, ada metode yang lebih baik. Masalahnya, kode ini tidak dapat menunjukkan tujuannya dengan jelas. Untuk membuatnya lebih mudah untuk memahami, dapat menggunakan kelas StringBuffer untuk mempack function ini:

function StringBuffer () {
  this._strings_ = new Array();
}
StringBuffer.prototype.append = function(str) {
  this._strings_.push(str);
};
StringBuffer.prototype.toString = function() {
  return this._strings_.join("");
};

Perhatian pertama dalam kode ini adalah atribut strings, yang berarti properti pribadi. Ia hanya memiliki dua metode, yaitu metode append() dan metode toString(). Metode append() memiliki satu parameter, yang menggabungkan parameter tersebut ke dalam array string, sedangkan metode toString() memanggil metode join array, yang mengembalikan string yang sebenarnya tergabung. Untuk menggabungkan beberapa string ke dalam objek StringBuffer, dapat digunakan kode berikut:

var buffer = new StringBuffer();
buffer.append("hello ");
buffer.append("world");
var result = buffer.toString();

Try It Yourself (TIY)

You can test the performance of the StringBuffer object and the traditional string concatenation method with the following code:

var d1 = new Date();
var str = "";
for (var i=0; i < 10000; i++) {
    str += "text";
}
var d2 = new Date();
document.write("Concatenation with plus: ")
 + (d2.getTime() - d1.getTime()) + " milliseconds");
var buffer = new StringBuffer();
d1 = new Date();
for (var i=0; i < 10000; i++) {
    buffer.append("text");
}
var result = buffer.toString();
d2 = new Date();
document.write("<br />Concatenation with StringBuffer: ")
 + (d2.getTime() - d1.getTime()) + " milliseconds");

Try It Yourself (TIY)

This code performs two tests on string concatenation, the first using the plus sign, and the second using the StringBuffer class. Each operation concatenates 10,000 strings. The date values d1 and d2 are used to determine the time required to complete the operation. Note that when creating a Date object without parameters, the current date and time are assigned to the object. To calculate how long the concatenation operation takes, simply subtract the millisecond representation of the dates (using the return value of the getTime() method). This is a common method for measuring JavaScript performance. The results of this test can help you compare the efficiency difference between using the StringBuffer class and using the plus sign.