2013-08-30 29 views
128

Tôi đã xem xét cách sử dụng phương pháp Object.defineProperty, nhưng không thể tìm thấy bất kỳ thứ gì tốt.cách sử dụng javascript Object.defineProperty

Có người đã cho tôi this snippet of code:

Object.defineProperty(player, "health", { 
    get: function() { 
     return 10 + (player.level * 15); 
    } 
}) 

Nhưng tôi không hiểu nó. Chủ yếu, các get là những gì tôi không thể có được (chơi chữ dự định). Làm thế nào nó hoạt động?

+0

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

Trả lời

386

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 getset 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ị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đế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 antipatternsmã 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.

21

get là một hàm được gọi khi bạn cố gắng để đọc các giá trị player.health, như trong:

console.log(player.health); 

Đó là một cách hiệu quả không khác nhau nhiều hơn:

player.getHealth = function(){ 
    return 10 + this.level*15; 
} 
console.log(player.getHealth()); 

Điều ngược lại của get được thiết lập , sẽ được sử dụng khi bạn gán cho giá trị. Vì không có setter, có vẻ như gán cho sức khỏe của người chơi không có ý định:

player.health = 5; // Doesn't do anything, since there is no set function defined 

Một ví dụ rất đơn giản:

var player = { 
 
    level: 5 
 
}; 
 

 
Object.defineProperty(player, "health", { 
 
    get: function() { 
 
    return 10 + (player.level * 15); 
 
    } 
 
}); 
 

 
console.log(player.health); // 85 
 
player.level++; 
 
console.log(player.health); // 100 
 

 
player.health = 5; // Does nothing 
 
console.log(player.health); // 100

+0

nó giống như một chức năng mà bạn không cần phải thực sự sử dụng '()' để gọi ... Tôi không hiểu ý tưởng khi họ phát minh ra điều này là gì. Các hàm hoàn toàn giống nhau: https://jsbin.com/bugipi/edit?js,console,output – vsync

+0

Tôi có thể yêu thích câu trả lời như thế nào ??? –

2

Về cơ bản, defineProperty là một phương pháp mà có 3 tham số - một đối tượng, một thuộc tính và một bộ mô tả. Điều gì đang xảy ra trong cuộc gọi cụ thể này là thuộc tính "health" của đối tượng player sẽ được gán cho 10 cộng 15 lần mức của đối tượng trình phát đó.

0

vâng không có chức năng hơn mở rộng cho thiết lập setter & getter đây là ví dụ của tôi Object.defineProperty (obj, tên, func)

var obj = {}; 
['data', 'name'].forEach(function(name) { 
    Object.defineProperty(obj, name, { 
     get : function() { 
      return 'setter & getter'; 
     } 
    }); 
}); 


console.log(obj.data); 
console.log(obj.name); 
0

Object.defineProperty() là một function..Its toàn cầu không có sẵn bên trong hàm mà khai báo đối tượng khác.Bạn sẽ phải sử dụng nó tĩnh ...

7

defineProperty là một phương pháp trên đối tượng cho phép bạn định cấu hình các thuộc tính để đáp ứng một số tiêu chí S. Đây là một ví dụ đơn giản với đối tượng nhân viên với hai thuộc tính firstName & lastName và nối thêm hai thuộc tính bằng cách ghi đè phương thức toString trên đối tượng.

var employee = { 
    firstName: "Jameel", 
    lastName: "Moideen" 
}; 
employee.toString=function() { 
    return this.firstName + " " + this.lastName; 
}; 
console.log(employee.toString()); 

Bạn sẽ nhận được đầu ra như: Jameel Moideen

Tôi sẽ thay đổi cùng mã bằng cách sử dụng defineProperty trên đối tượng

var employee = { 
    firstName: "Jameel", 
    lastName: "Moideen" 
}; 
Object.defineProperty(employee, 'toString', { 
    value: function() { 
     return this.firstName + " " + this.lastName; 
    }, 
    writable: true, 
    enumerable: true, 
    configurable: true 
}); 
console.log(employee.toString()); 

Tham số đầu tiên là tên của đối tượng và sau đó tham số thứ hai là tên của thuộc tính mà chúng ta đang thêm vào, trong trường hợp của chúng ta là toString và sau đó tham số cuối cùng là đối tượng json có một giá trị sẽ là một hàm và ba tham số có thể ghi, liệt kê và đồng nfigurable.Right bây giờ tôi chỉ tuyên bố tất cả mọi thứ là đúng sự thật.

Nếu u chạy ví dụ bạn sẽ nhận được đầu ra như: Jameel Moideen

Hãy hiểu lý do tại sao chúng ta cần ba thuộc tính như ghi, đếm được và cấu hình. ghi Một trong những phần rất khó chịu của javascript là, nếu bạn thay đổi thuộc tính toString để cái gì khác ví dụ enter image description here

nếu chạy này một lần nữa, mọi thứ đều trở nên phá vỡ Hãy thay đổi có thể ghi thành false. Nếu chạy cùng một lần nữa, bạn sẽ nhận được kết quả chính xác là ‘Jameel Moideen’. Thuộc tính này sẽ ngăn ghi đè thuộc tính này sau. đếm nếu bạn in tất cả các phím bên trong đối tượng, bạn có thể xem tất cả các thuộc tính bao gồm toString.

console.log(Object.keys(employee)); 

enter image description here

nếu bạn thiết lập đếm được để sai, bạn có thể ẩn toString tài sản từ người khác. Nếu chạy lại lần nữa, bạn sẽ nhận được firstName, lastName cấu hình

nếu ai đó sau đó định nghĩa lại đối tượng về sau ví dụ như đếm được đúng và chạy nó. Bạn có thể thấy thuộc tính toString đã xuất hiện trở lại.

var employee = { 
    firstName: "Jameel", 
    lastName: "Moideen" 
}; 
Object.defineProperty(employee, 'toString', { 
    value: function() { 
     return this.firstName + " " + this.lastName; 
    }, 
    writable: false, 
    enumerable: false, 
    configurable: true 
}); 

//change enumerable to false 
Object.defineProperty(employee, 'toString', { 

    enumerable: true 
}); 
employee.toString="changed"; 
console.log(Object.keys(employee)); 

enter image description here

bạn có thể hạn chế hành vi này bằng cách thiết lập cấu hình để sai.

Orginal reference of this information is from my personal Blog

+1

Tôi nhận được rằng bạn đã có điều này trên blog của bạn và chỉ dán nó ở đây, nhưng ít nhất là biết điều này cho tương lai: screencaps không phổ biến trên SO. Bạn không thể copypaste mã để thử nó và mã sẽ không được nhìn thấy bởi công cụ tìm kiếm hoặc công nghệ hỗ trợ. –

+0

@JacqueGoupil Bạn nói đúng.i sẽ cập nhật bằng cách thêm mã thay vì ảnh chụp màn hình – JEMI

Các vấn đề liên quan