Vì bạn đã hỏi similar question, hãy thực hiện từng bước. Đó là lâu hơn một chút, nhưng nó có thể tiết kiệm cho bạn nhiều thời gian hơn tôi đã dành cho văn bản này:
Đặc tính là một tính năng OOP được thiết kế để tách mã khách hàng. Ví dụ, trong một số e-shop bạn có thể có các đối tượng như thế này:
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}
Sau đó, trong mã khách hàng của bạn (e-shop), bạn có thể thêm giảm giá cho sản phẩm của bạn:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Sau , chủ sở hữu cửa hàng điện tử có thể nhận thấy rằng chiết khấu không thể lớn hơn 80%. Bây giờ bạn cần phải tìm EVERY xuất hiện của việc sửa đổi giảm giá trong các mã khách hàng và thêm một dòng
if(obj.discount>80) obj.discount = 80;
Sau đó, chủ sở hữu e-shop hơn nữa có thể thay đổi chiến lược của mình, giống như "nếu khách hàng là đại lý bán lẻ, chiết khấu tối đa có thể là 90% ". Và bạn cần phải thực hiện thay đổi ở nhiều nơi một lần nữa cộng với việc bạn cần phải nhớ thay đổi những dòng này bất cứ khi nào chiến lược được thay đổi. Đây là một thiết kế tồi. Đó là lý do tại sao đóng gói là nguyên tắc cơ bản của OOP.Nếu các nhà xây dựng là như thế này:
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; }
}
Sau đó, bạn chỉ có thể thay đổi getDiscount
(accessor) và setDiscount
(mutator) phương pháp. Vấn đề là hầu hết các thành viên hoạt động giống như các biến thông thường, chỉ việc giảm giá cần được chăm sóc đặc biệt ở đây. Nhưng thiết kế tốt yêu cầu đóng gói của mọi thành viên dữ liệu để giữ mã mở rộng. Vì vậy, bạn cần thêm nhiều mã không có gì. Đây cũng là thiết kế tồi, một mẫu đối tượng mẫu boilerplate. Đôi khi bạn không thể chỉ refactor các lĩnh vực để phương pháp sau này (mã eshop có thể phát triển lớn hoặc một số mã bên thứ ba có thể phụ thuộc vào phiên bản cũ), do đó, boilerplate là ít ác ở đây. Nhưng vẫn còn, đó là điều ác. Đó là lý do tại sao các thuộc tính được đưa vào nhiều ngôn ngữ. Bạn có thể giữ các mã gốc, chỉ cần thay đổi các thành viên giảm giá thành một tài sản với get
và set
khối:
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
Lưu ý cuối cùng nhưng một dòng: trách nhiệm về giá trị chiết khấu đúng đã được chuyển từ các mã khách hàng (e- định nghĩa cửa hàng) cho định nghĩa sản phẩm. Sản phẩm chịu trách nhiệm giữ cho các thành viên dữ liệu của nó nhất quán. Thiết kế tốt là (gần như đã nói) nếu mã hoạt động giống như suy nghĩ của chúng ta.
Rất nhiều về thuộc tính. Nhưng javascript là khác nhau từ tinh khiết ngôn ngữ hướng đối tượng như C# và mã các tính năng khác nhau:
Trong C#, chuyển trường vào tính chất là một breaking change, vì vậy các lĩnh vực công cộng nên được mã hóa như Auto-Implemented Properties nếu mã của bạn có thể được sử dụng trong khách hàng được biên dịch tách biệt.
Trong Javascript, các thuộc tính tiêu chuẩn (thành viên dữ liệu với phương thức getter và setter mô tả ở trên) được định nghĩa bởi mô tả accessor (trong liên kết mà bạn có trong câu hỏi của bạn). Cụ thể, bạn có thể sử dụng mô tả dữ liệu (vì vậy, bạn không thể sử dụng tức làgiá trị và thiết trên cùng một tài sản):
- mô tả accessor = get + thiết lập (xem ví dụ trên)
- được phải là một chức năng; giá trị trả lại của nó được sử dụng để đọc thuộc tính; nếu không quy định, mặc định là không xác định, mà hoạt động như một hàm trả về xác định
- thiết phải là một chức năng; tham số của nó được lấp đầy với RHS trong việc ấn định một giá trị cho thuộc tính; nếu không quy định, mặc định là không xác định, mà hoạt động như một chức năng rỗng
- mô tả dữ liệu = value + ghi (xem ví dụ dưới đây)
- giá trị mặc định không xác định; nếu ghi, cấu hình và đếm (xem dưới đây) là đúng sự thật, khách sạn sẽ hoạt động như một trường dữ liệu bình thường
- ghi - mặc sai; nếu không đúng, thuộc tính chỉ đọc; cố gắng viết được bỏ qua mà không có lỗi *!
Cả hai mô tả có thể có các thành viên:
- cấu hình - mặc sai; nếu không đúng, không thể xóa thuộc tính; cố gắng xóa được bỏ qua mà không có lỗi *!
- enumerable - mặc định false; nếu đúng, nó sẽ được lặp lại trong
for(var i in theObject)
; nếu sai sự thật, nó sẽ không được lặp, nhưng nó vẫn còn truy cập như công
* trừ khi trong strict mode - trong trường hợp đó JS dừng thực hiện với TypeError trừ khi nó được bắt trong try-catch block
Để đọc những cài đặt, sử dụng Object.getOwnPropertyDescriptor()
.
Tìm hiểu bằng ví dụ:
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
Nếu bạn không muốn để cho phép mã khách hàng lận như vậy, bạn có thể hạn chế các đối tượng bằng ba cấp độ ở trại giam:
- Object.preventExtensions(yourObject) ngăn chặn các thuộc tính mới sẽ được thêm vào yourObject. Sử dụng
Object.isExtensible(<yourObject>)
để kiểm tra xem phương pháp đã được sử dụng trên đối tượng chưa. Phòng ngừa là nông (đọc bên dưới).
- Object.seal(yourObject) giống như trên và không thể xóa thuộc tính (có hiệu quả là
configurable: false
đối với tất cả các thuộc tính). Sử dụng Object.isSealed(<yourObject>)
để phát hiện tính năng này trên đối tượng. Con dấu là nông (đọc bên dưới).
- Object.freeze(yourObject) giống như trên và không thể thay đổi thuộc tính (đặt hiệu quả
writable: false
cho tất cả các thuộc tính có bộ mô tả dữ liệu). Tài sản có thể ghi của Setter không bị ảnh hưởng (vì nó không có). Đóng băng là nông: có nghĩa là nếu thuộc tính là Đối tượng, thuộc tính của nó KHÔNG bị đóng băng (nếu bạn muốn, bạn nên thực hiện một cái gì đó như "đóng băng sâu", tương tự như deep copy - cloning). Sử dụng Object.isFrozen(<yourObject>)
để phát hiện.
Bạn không cần phải bận tâm với điều này nếu bạn chỉ viết một vài dòng thú vị. Nhưng nếu bạn muốn mã hóa một trò chơi (như bạn đã đề cập trong câu hỏi được liên kết), bạn nên thực sự quan tâm đến thiết kế tốt. Hãy thử google điều gì đó về số điện thoại antipatterns và mã số. Nó sẽ giúp bạn tránh các tình huống như "Ồ, tôi cần phải viết lại hoàn toàn mã của tôi một lần nữa!", nó có thể giúp bạn tiết kiệm nhiều tháng tuyệt vọng nếu bạn muốn viết mã nhiều. Chúc may mắn.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty đây là một hướng dẫn tuyệt vời ở đây. – Martian2049