เมื่อคุณถามคำถามที่คล้ายกันลองทำทีละขั้นตอน อีกหน่อย แต่มันอาจช่วยให้คุณประหยัดเวลาได้มากกว่าที่ฉันเขียนไว้:
คุณสมบัติเป็นคุณสมบัติ OOP ที่ออกแบบมาเพื่อแยกรหัสลูกค้าได้อย่างสมบูรณ์ ตัวอย่างเช่นในร้านค้าออนไลน์บางแห่งคุณอาจมีวัตถุเช่นนี้:
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}
จากนั้นในรหัสลูกค้าของคุณ (e-shop) คุณสามารถเพิ่มส่วนลดให้กับผลิตภัณฑ์ของคุณ:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
ต่อมาเจ้าของ e-shop อาจตระหนักว่าส่วนลดนั้นไม่สามารถมากกว่า 80% ได้ ตอนนี้คุณต้องค้นหาการเกิดส่วนลดทุกอย่างในรหัสลูกค้าและเพิ่มบรรทัด
if(obj.discount>80) obj.discount = 80;
จากนั้นเจ้าของร้าน E-ต่อไปอาจมีการเปลี่ยนแปลงกลยุทธ์ของเขาเช่น"ถ้าลูกค้าเป็นผู้ค้าปลีก, ส่วนลดสูงสุดสามารถเป็น 90%" และคุณต้องทำการเปลี่ยนแปลงในหลาย ๆ สถานที่อีกครั้งและคุณต้องจำไว้ว่าต้องเปลี่ยนบรรทัดเหล่านี้ทุกครั้งที่มีการเปลี่ยนแปลงกลยุทธ์ นี่คือการออกแบบที่ไม่ดี นั่นเป็นเหตุผลที่encapsulationเป็นหลักการพื้นฐานของ OOP หากตัวสร้างเป็นเช่นนี้:
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; }
}
จากนั้นคุณสามารถเปลี่ยนวิธีgetDiscount
( accessor ) และsetDiscount
( mutator ) ปัญหาคือสมาชิกส่วนใหญ่ทำตัวเหมือนตัวแปรทั่วไปเพียงแค่ส่วนลดต้องได้รับการดูแลเป็นพิเศษที่นี่ แต่การออกแบบที่ดีต้องมีการห่อหุ้มข้อมูลของสมาชิกทุกคนเพื่อให้สามารถขยายโค้ดได้ ดังนั้นคุณต้องเพิ่มโค้ดจำนวนมากที่ไม่ทำอะไรเลย นี้คือการออกแบบที่ไม่ดีantipattern สำเร็จรูป บางครั้งคุณไม่สามารถปรับเปลี่ยนเขตข้อมูลให้เป็นวิธีการในภายหลัง (รหัส eshop อาจขยายใหญ่ขึ้นหรือรหัสบุคคลที่สามบางอย่างอาจขึ้นอยู่กับรุ่นเก่า) ดังนั้นแผ่นเหล็กสำเร็จรูปจึงมีความชั่วร้ายน้อยลงที่นี่ แต่ถึงกระนั้นมันก็เป็นความชั่วร้าย นั่นเป็นสาเหตุที่คุณสมบัติถูกนำมาใช้ในหลายภาษา คุณสามารถเก็บรหัสเดิมเพียงแค่เปลี่ยนสมาชิกส่วนลดให้เป็นทรัพย์สินด้วยget
และset
บล็อก:
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
หมายเหตุบรรทัดสุดท้าย แต่หนึ่งบรรทัด: ความรับผิดชอบสำหรับมูลค่าส่วนลดที่ถูกต้องถูกย้ายจากรหัสลูกค้า (การกำหนดร้านค้าอิเล็กทรอนิกส์) ไปยังคำจำกัดความของผลิตภัณฑ์ ผลิตภัณฑ์มีหน้าที่รักษาข้อมูลสมาชิกให้คงที่ การออกแบบที่ดีคือ (พูดโดยคร่าว ๆ ) หากรหัสทำงานในลักษณะเดียวกับความคิดของเรา
มากเกี่ยวกับคุณสมบัติ แต่จาวาสคริปต์นั้นแตกต่างจากภาษาเชิงวัตถุล้วนๆเช่น C # และรหัสคุณลักษณะต่างกัน:
ใน C # การแปลงเขตข้อมูลเป็นคุณสมบัติเป็นการเปลี่ยนแปลงที่ไม่สมบูรณ์ดังนั้นเขตข้อมูลสาธารณะควรถูกเข้ารหัสเป็นคุณสมบัติที่มีการใช้งานอัตโนมัติหากรหัสของคุณอาจถูกใช้ในไคลเอนต์ที่คอมไพล์แล้วแยกกัน
ใน Javascriptคุณสมบัติมาตรฐาน (สมาชิกข้อมูลที่มี getter และ setter อธิบายไว้ข้างต้น) ถูกกำหนดโดยตัวอธิบาย accessor (ในลิงก์ที่คุณมีในคำถามของคุณ) โดยเฉพาะคุณสามารถใช้data descriptor (ดังนั้นคุณไม่สามารถใช้ค่า ie และตั้งค่าในคุณสมบัติเดียวกัน):
- accessor descriptor = get + set (ดูตัวอย่างด้านบน)
- รับจะต้องมีฟังก์ชั่น; ส่งคืนค่าที่ใช้ในการอ่านคุณสมบัติ หากไม่ได้ระบุไว้ค่าเริ่มต้นจะไม่ได้กำหนดซึ่งจะทำหน้าที่เหมือนฟังก์ชันที่ส่งคืนค่าที่ไม่ได้กำหนด
- ชุดต้องเป็นฟังก์ชั่น; พารามิเตอร์ของมันเต็มไปด้วย RHS ในการกำหนดค่าให้กับคุณสมบัติ หากไม่ได้ระบุไว้ค่าเริ่มต้นจะไม่ได้กำหนดซึ่งจะทำหน้าที่เหมือนฟังก์ชันที่ว่างเปล่า
- data descriptor = value + ที่เขียนได้ (ดูตัวอย่างด้านล่าง)
- ค่าเริ่มต้นไม่ได้กำหนด ; ถ้าเขียนได้ ,การกำหนดค่าและนับ (ดูด้านล่าง) เป็นจริงพฤติกรรมของสถานที่ให้บริการเช่นเขตข้อมูลสามัญ
- เขียนได้ -เท็จเริ่มต้น; หากไม่เป็นจริงคุณสมบัติจะอ่านได้อย่างเดียว ความพยายามในการเขียนถูกละเว้นโดยไม่มีข้อผิดพลาด *!
ตัวอธิบายทั้งสองสามารถมีสมาชิกเหล่านี้:
- กำหนดค่าได้ -เท็จเริ่มต้น; หากไม่เป็นจริงคุณสมบัติจะไม่สามารถลบได้ ความพยายามในการลบถูกละเว้นโดยไม่มีข้อผิดพลาด *!
- นับได้ -เท็จเริ่มต้น; ถ้าเป็นจริงก็จะมีการซ้ำใน
for(var i in theObject)
; หากเป็นเท็จจะไม่มีการทำซ้ำ แต่ยังสามารถเข้าถึงได้แบบสาธารณะ
* ยกเว้นในโหมดเข้มงวด - ในกรณีนั้น JS หยุดการดำเนินการกับ TypeError เว้นแต่ว่าจะอยู่ในบล็อก try-catch
Object.getOwnPropertyDescriptor()
อ่านการตั้งค่าเหล่านี้ใช้
เรียนรู้ตามตัวอย่าง:
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
หากคุณไม่ต้องการให้รหัสไคลเอนต์โกงเช่นนี้คุณสามารถ จำกัด ออบเจ็กต์ด้วยการคุมขังสามระดับ:
- Object.preventExtensions (yourObject)ป้องกันไม่ให้คุณสมบัติใหม่ที่จะเพิ่มyourObject ใช้
Object.isExtensible(<yourObject>)
เพื่อตรวจสอบว่ามีการใช้วิธีการนี้กับวัตถุหรือไม่ การป้องกันตื้น (อ่านด้านล่าง)
- Object.seal (yourObject)เหมือนกับด้านบนและไม่สามารถลบคุณสมบัติได้ (ตั้งค่า
configurable: false
เป็นคุณสมบัติทั้งหมดได้อย่างมีประสิทธิภาพ) ใช้Object.isSealed(<yourObject>)
เพื่อตรวจจับคุณสมบัตินี้บนวัตถุ ตราประทับนั้นตื้น (อ่านด้านล่าง)
- Object.freeze (yourObject)เหมือนกับด้านบนและไม่สามารถเปลี่ยนแปลงคุณสมบัติได้ (กำหนด
writable: false
ให้คุณสมบัติทั้งหมดที่มี data descriptor ได้อย่างมีประสิทธิภาพ) คุณสมบัติการเขียนของ Setter ไม่ได้รับผลกระทบ (เนื่องจากไม่มี) การแช่แข็งนั้นตื้น : หมายความว่าถ้าคุณสมบัติเป็นวัตถุคุณสมบัติของมันจะไม่ถูกแช่แข็ง (ถ้าคุณต้องการคุณควรดำเนินการบางอย่างเช่น "แช่แข็งลึก" ซึ่งคล้ายกับการทำสำเนาลึก ) ใช้Object.isFrozen(<yourObject>)
ในการตรวจจับ
คุณไม่จำเป็นต้องกังวลกับเรื่องนี้ถ้าคุณเขียนสนุกเพียงไม่กี่บรรทัด แต่ถ้าคุณต้องการโค้ดเกม (ดังที่คุณกล่าวถึงในคำถามที่เชื่อมโยง) คุณควรใส่ใจกับการออกแบบที่ดีจริงๆ ลอง google บางอย่างเกี่ยวกับantipatternsและกลิ่นรหัส มันจะช่วยให้คุณหลีกเลี่ยงสถานการณ์เช่น"โอ้ฉันต้องเขียนโค้ดใหม่อีกครั้ง!" มันสามารถประหยัดเวลาหลายเดือนที่คุณสิ้นหวังถ้าคุณต้องการรหัสมาก โชคดี.