Vì bạn đã hỏi một câu hỏi tương tự , chúng ta hãy thực hiện từng bước một. Nó dài hơn một chút, nhưng nó có thể giúp bạn tiết kiệm nhiều thời gian hơn tôi đã dành cho việc viết bài này:
Thuộc tính là một tính năng OOP được thiết kế để phân tách sạch mã khách hàng. Ví dụ: trong một số cửa hàng điện tử, 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 (cửa hàng điện tử), bạn có thể thêm giảm giá cho các sản phẩm của mình:
function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Sau đó, chủ cửa hàng điện tử có thể nhận ra rằng mức giảm giá không thể lớn hơn 80%. Bây giờ bạn cần tìm MỌI sự xuất hiện của sửa đổi giảm giá trong mã máy khách và thêm một dòng
if(obj.discount>80) obj.discount = 80;
Sau đó, chủ cửa hàng điện tử có thể thay đổi thêm chiến lược của mình, như "nếu khách hàng là người bán lại, mức chiết khấu tối đa có thể là 90%" . Và bạn cần thực hiện thay đổi trên nhiều nơi một lần nữa cộng với bạn cần nhớ thay đổi các dòng này bất cứ khi nào chiến lược được thay đổi. Đây là một thiết kế xấu. Đó là lý do tại sao đóng gói là nguyên tắc cơ bản của OOP. Nếu 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 các phương thức getDiscount
( accessor ) và setDiscount
( mutator ). Vấn đề là hầu hết các thành viên hành xử giống như các biến thông thường, chỉ cần giảm giá cần được chăm sóc đặc biệt ở đây. Nhưng thiết kế tốt đòi hỏi phải đóng gói mọi thành viên dữ liệu để giữ cho mã có thể mở rộng. Vì vậy, bạn cần thêm nhiều mã mà không làm gì cả. Đây cũng là một thiết kế tồi, một antipotype . Đôi khi, bạn không thể cấu trúc lại các trường thành các phương thức sau này (mã eshop có thể phát triển lớn hoặc một số mã của bên thứ ba có thể phụ thuộc vào phiên bản cũ), do đó, bản tóm tắt ít tệ hơn ở đây. Nhưng vẫn là ác. Đó là lý do tại sao tài sản được giới thiệu vào nhiều ngôn ngữ. Bạn có thể giữ mã gốc, chỉ cần chuyển đổi thành viên giảm giá thành tài sản vớiget
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 đối với giá trị chiết khấu chính xác đã được chuyển từ mã khách hàng (định nghĩa cửa hàng điện tử) sang định nghĩa sản phẩm. Sản phẩm có 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à (đại khái) nếu mã hoạt động giống như suy nghĩ của chúng tôi.
Rất nhiều về tài sản. Nhưng javascript khác với các ngôn ngữ hướng đối tượng thuần túy như C # và mã hóa các tính năng khác nhau:
Trong C # , chuyển đổi các trường thành các thuộc tính là một thay đổi đột phá , vì vậy các trường công khai phải được mã hóa thành Thuộc tính được triển khai tự động nếu mã của bạn có thể được sử dụng trong máy khách được biên dịch riêng biệt.
Trong Javascript , các thuộc tính tiêu chuẩn (thành viên dữ liệu với getter và setter được mô tả ở trên) được xác định bởi bộ mô tả truy cập (trong liên kết bạn có trong câu hỏi của mình). Độc quyền, bạn có thể sử dụng mô tả dữ liệu (vì vậy bạn không thể sử dụng giá trị tức là và đặt trên cùng một thuộc tính):
- accessor descriptor = get + set (xem ví dụ ở trên)
- nhận được phải là một chức năng; giá trị trả lại của nó được sử dụng trong việc đọc tài sản; nếu không được chỉ định, mặc định là không xác định , hoạt động giống như một hàm trả về không xác định
- thiết lập phải là một chức năng; tham số của nó chứa đầy RHS trong việc gán giá trị cho thuộc tính; nếu không được chỉ định, mặc định là không xác định , hoạt động như một hàm trống
- mô tả dữ liệu = giá trị + có thể ghi (xem ví dụ bên dưới)
- giá trị mặc định không xác định ; nếu có thể ghi , cấu hình và liệt kê (xem bên dưới) là đúng, thuộc tính hoạt động như một trường dữ liệu thông thường
- có thể ghi - mặc định sai ; nếu không đúng , tài sản chỉ được đọc; cố gắng viết bị bỏ qua mà không có lỗi *!
Cả hai mô tả có thể có các thành viên này:
- cấu hình - mặc định sai ; nếu không đúng sự thật, tài sản không thể bị xóa; cố gắng xóa được bỏ qua mà không có lỗi *!
- vô số - mặc định sai ; nếu đúng, nó sẽ được lặp lại
for(var i in theObject)
; nếu sai, nó sẽ không bị lặp lại, nhưng nó vẫn có thể truy cập được dưới dạng công khai
* trừ khi ở chế độ nghiêm ngặt - trong trường hợp đó, JS dừng thực thi với TypeError trừ khi nó bị bắt trong khối thử bắt
Để đọc các cài đặt này, 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ã máy khách lừa đảo như vậy, bạn có thể hạn chế đối tượng theo ba cấp độ giam cầm:
- Object.preventExtensions (yourObject) ngăn các thuộc tính mới được thêm vào yourObject . Sử dụng
Object.isExtensible(<yourObject>)
để kiểm tra xem phương thức đã được sử dụng trên đối tượng chưa. Phòng ngừa là nông (đọc dưới đây).
- Object.seal (yourObject) giống như trên và các thuộc tính không thể bị xóa (thiết lập hiệu quả
configurable: false
cho tất cả các thuộc tính). Sử dụngObject.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à các thuộc tính không thể thay đổi (thiết lập hiệu quả
writable: false
cho tất cả các thuộc tính với 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ó tài sản). Đóng băng là nông : có nghĩa là nếu thuộc tính là Object, 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ư sao chép sâu - nhân bản ). Sử dụngObject.isFrozen(<yourObject>)
để phát hiện nó.
Bạn không cần bận tâm đến điều này nếu bạn chỉ viết vài dòng vui vẻ. Nhưng nếu bạn muốn viết mã 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 một cái gì đó về antipotype và mùi mã . 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 mình một lần nữa!" , nó có thể giúp bạn tiết kiệm hàng tháng tuyệt vọng nếu bạn muốn viết mã nhiều. Chúc may mắn.