Karena Anda mengajukan pertanyaan serupa , mari selangkah demi selangkah. Ini sedikit lebih lama, tetapi mungkin menghemat lebih banyak waktu daripada yang saya habiskan untuk menulis ini:
Properti adalah fitur OOP yang dirancang untuk pemisahan kode klien secara bersih. Misalnya, di beberapa toko elektronik Anda mungkin memiliki objek seperti ini:
function Product(name,price) {
this.name = name;
this.price = price;
this.discount = 0;
}
var sneakers = new Product("Sneakers",20); // {name:"Sneakers",price:20,discount:0}
var tshirt = new Product("T-shirt",10); // {name:"T-shirt",price:10,discount:0}
Kemudian dalam kode klien Anda (toko online), Anda dapat menambahkan diskon ke produk Anda:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Kemudian, pemilik e-shop mungkin menyadari bahwa diskon tidak boleh lebih besar dari mengatakan 80%. Sekarang Anda perlu menemukan SETIAP kemunculan modifikasi diskon dalam kode klien dan menambahkan baris
if(obj.discount>80) obj.discount = 80;
Kemudian pemilik e-shop lebih lanjut dapat mengubah strateginya, seperti "jika pelanggan adalah reseller, diskon maksimal bisa 90%" . Dan Anda perlu melakukan perubahan di beberapa tempat lagi ditambah Anda harus ingat untuk mengubah garis-garis ini kapan saja strategi diubah. Ini desain yang buruk. Itu sebabnya enkapsulasi adalah prinsip dasar OOP. Jika konstruktornya seperti ini:
function Product(name,price) {
var _name=name, _price=price, _discount=0;
this.getName = function() { return _name; }
this.setName = function(value) { _name = value; }
this.getPrice = function() { return _price; }
this.setPrice = function(value) { _price = value; }
this.getDiscount = function() { return _discount; }
this.setDiscount = function(value) { _discount = value; }
}
Kemudian Anda bisa mengubah metode getDiscount
( accessor ) dan setDiscount
( mutator ). Masalahnya adalah bahwa sebagian besar anggota berperilaku seperti variabel umum, hanya diskon membutuhkan perawatan khusus di sini. Tetapi desain yang baik membutuhkan enkapsulasi dari setiap anggota data untuk menjaga kode dapat diperluas. Jadi, Anda perlu menambahkan banyak kode yang tidak melakukan apa-apa. Ini juga desain yang buruk, antipattern boilerplate . Kadang-kadang Anda tidak bisa hanya refactor bidang ke metode nanti (kode eshop dapat tumbuh besar atau beberapa kode pihak ketiga mungkin tergantung pada versi lama), sehingga boilerplate lebih sedikit jahat di sini. Tapi tetap saja, itu jahat. Itu sebabnya properti diperkenalkan ke banyak bahasa. Anda dapat menyimpan kode asli, cukup mengubah anggota diskon menjadi properti denganget
dan set
blok:
function Product(name,price) {
this.name = name;
this.price = price;
//this.discount = 0; // <- remove this line and refactor with the code below
var _discount; // private member
Object.defineProperty(this,"discount",{
get: function() { return _discount; },
set: function(value) { _discount = value; if(_discount>80) _discount = 80; }
});
}
// the client code
var sneakers = new Product("Sneakers",20);
sneakers.discount = 50; // 50, setter is called
sneakers.discount+= 20; // 70, setter is called
sneakers.discount+= 20; // 80, not 90!
alert(sneakers.discount); // getter is called
Perhatikan baris terakhir tetapi satu: tanggung jawab untuk nilai diskon yang benar dipindahkan dari kode klien (definisi e-shop) ke definisi produk. Produk bertanggung jawab untuk menjaga anggota datanya konsisten. Desain yang bagus adalah (secara kasar dikatakan) jika kode bekerja dengan cara yang sama dengan pikiran kita.
Begitu banyak tentang properti. Tapi javascript berbeda dari bahasa berorientasi objek murni seperti C # dan kode fitur-fiturnya berbeda:
Di C # , mentransformasikan bidang menjadi properti merupakan perubahan besar , jadi bidang publik harus dikodekan sebagai Properti yang Diimplementasikan Otomatis jika kode Anda mungkin digunakan dalam klien yang dikompilasi secara terpisah.
Dalam Javascript , properti standar (anggota data dengan pengambil dan penyetel yang dijelaskan di atas) ditentukan oleh deskriptor accessor (dalam tautan yang Anda miliki dalam pertanyaan Anda). Secara eksklusif, Anda dapat menggunakan deskriptor data (sehingga Anda tidak dapat menggunakan nilai yaitu dan disetel pada properti yang sama):
- deskriptor accessor = dapatkan + set (lihat contoh di atas)
- dapatkan harus menjadi fungsi; nilai pengembaliannya digunakan dalam membaca properti; jika tidak ditentukan, defaultnya adalah tidak terdefinisi , yang berperilaku seperti fungsi yang mengembalikan tidak terdefinisi
- set harus berupa fungsi; parameternya diisi dengan RHS dalam memberikan nilai ke properti; jika tidak ditentukan, standarnya tidak ditentukan , yang berperilaku seperti fungsi kosong
- deskriptor data = nilai + dapat ditulis (lihat contoh di bawah)
- nilai default tidak terdefinisi ; jika dapat ditulis , dapat dikonfigurasi ,dan dapat dihitung (lihat di bawah) adalah benar, properti berperilaku seperti bidang data biasa
- dapat ditulis - default salah ; jika tidak benar , properti hanya dibaca; usaha menulis diabaikan tanpa kesalahan *!
Kedua deskriptor dapat memiliki anggota ini:
- dapat dikonfigurasi - default salah ; jika tidak benar, properti tidak dapat dihapus; upaya untuk menghapus diabaikan tanpa kesalahan *!
- enumerable - default false ; jika benar, itu akan diulang di
for(var i in theObject)
; jika salah, itu tidak akan diulangi, tetapi masih dapat diakses sebagai publik
* kecuali dalam mode ketat - dalam hal ini JS menghentikan eksekusi dengan TypeError kecuali jika tertangkap dalam blok try-catch
Untuk membaca pengaturan ini, gunakan Object.getOwnPropertyDescriptor()
.
Belajar dengan contoh:
var o = {};
Object.defineProperty(o,"test",{
value: "a",
configurable: true
});
console.log(Object.getOwnPropertyDescriptor(o,"test")); // check the settings
for(var i in o) console.log(o[i]); // nothing, o.test is not enumerable
console.log(o.test); // "a"
o.test = "b"; // o.test is still "a", (is not writable, no error)
delete(o.test); // bye bye, o.test (was configurable)
o.test = "b"; // o.test is "b"
for(var i in o) console.log(o[i]); // "b", default fields are enumerable
Jika Anda tidak ingin mengizinkan kode klien menipu seperti itu, Anda dapat membatasi objek dengan tiga tingkat kurungan:
- Object.preventExtensions (yourObject) mencegah properti baru ditambahkan ke yourObject . Gunakan
Object.isExtensible(<yourObject>)
untuk memeriksa apakah metode itu digunakan pada objek. Pencegahannya dangkal (baca di bawah).
- Object.seal (yourObject) sama seperti di atas dan properti tidak dapat dihapus (secara efektif disetel
configurable: false
ke semua properti). GunakanObject.isSealed(<yourObject>)
untuk mendeteksi fitur ini pada objek. Segel itu dangkal (baca di bawah).
- Object.freeze (yourObject) sama seperti di atas dan properti tidak dapat diubah (secara efektif disetel
writable: false
ke semua properti dengan deskriptor data). Properti Setter yang dapat ditulisi tidak terpengaruh (karena tidak memilikinya). Pembekuan itu dangkal : itu berarti bahwa jika properti adalah Obyek, propertinya TIDAK beku (jika Anda mau, Anda harus melakukan sesuatu seperti "deep freeze", mirip dengan kloning penyalinan dalam ). GunakanObject.isFrozen(<yourObject>)
untuk mendeteksinya.
Anda tidak perlu repot dengan ini jika Anda menulis hanya beberapa baris yang menyenangkan. Tetapi jika Anda ingin membuat kode permainan (seperti yang Anda sebutkan dalam pertanyaan terkait), Anda harus benar-benar peduli dengan desain yang bagus. Coba Google sesuatu tentang antipatterns dan kode bau . Ini akan membantu Anda menghindari situasi seperti "Oh, saya harus menulis ulang kode saya sepenuhnya!" , itu bisa menyelamatkanmu dari keputusasaan selama berbulan-bulan jika ingin banyak kode. Semoga berhasil.