Cách sử dụng javascript Object.defineProperty


183

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

Ai đó đã cho tôi đoạn mã này :

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

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


1
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Khăn đây là một hướng dẫn tuyệt vời ở đây.
Martian2049

Câu trả lời:


499

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ớigetsetkhố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 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ạifor(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ụngObject.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: falsecho 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: falsecho 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ề antipotypemù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.


Phần này là rõ ràng. function Product(name,price) { this.name = name; this.price = price; var _discount; // private member Object.defineProperty(this,"discount",{ get: function() { return _discount; }, set: function(value) { _discount = value; if(_discount>80) _discount = 80; } }); } 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
abu abu

27

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

console.log(player.health);

Nó thực sự không khác nhiều so với:

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

Đối diện của get được đặt, sẽ được sử dụng khi bạn gán cho giá trị. Vì không có setter, nên dường như việc gán cho sức khỏe của người chơi không nhằm mục đích:

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


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

15

notifyProperty là một phương thức trên Object cho phép bạn định cấu hình các thuộc tính để đáp ứng một số tiêu chí. Dưới đây là một ví dụ đơn giản với một đối tượng nhân viên có 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 Kết quả là: Jameel Moideen

Tôi sẽ thay đổi cùng một mã bằng cách sử dụng notifyProperty 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 chúng ta đang thêm, trong trường hợp của chúng tôi là toString và sau đó tham số cuối cùng là đối tượng json có giá trị sẽ là một hàm và ba tham số có thể ghi được, có thể đếm được và cấu hình. Bây giờ tôi chỉ tuyên bố mọi thứ là đúng.

Nếu bạn chạy ví dụ, bạn sẽ nhận được Kết quả là: Jameel Moideen

Chúng ta hãy hiểu tại sao chúng ta cần ba thuộc tính như có thể ghi, liệt kê và cấu hình.

có thể 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 thành một thứ khác chẳng hạn

nhập mô tả hình ảnh ở đây

nếu bạn chạy lại cái này, mọi thứ sẽ bị phá vỡ. Hãy thay đổi ghi thành sai. Nếu chạy lại như cũ, bạn sẽ nhận được đầu ra chính xác là 'Jameel Moideen'. Tài sản này sẽ ngăn chặn ghi đè tài sản này sau.

vô số

nếu bạn in tất cả các khóa bên trong đối tượng, bạn có thể thấy tất cả các thuộc tính bao gồm toString.

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

nhập mô tả hình ảnh ở đây

nếu bạn đặt vô số thành false, bạn có thể ẩn thuộc tính toString khỏi mọi người khác. Nếu chạy lại, bạn sẽ nhận được FirstName, lastName

cấu hình

nếu ai đó sau đó xác định lại đối tượng về sau, ví dụ như vô số thành đúng và chạy nó. Bạn có thể thấy tài sản toString đã 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));

nhập mô tả hình ảnh ở đây

bạn có thể hạn chế hành vi này bằng cách đặt cấu hình thành false.

Tài liệu tham khảo chính của thông tin này là từ Blog cá nhân của tôi


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

@JacqueGoupil Bạn đã đúng. Tôi sẽ cập nhật bằng cách thêm mã thay vì chụp màn hình
Code-EZ

3

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


0

có, không có thêm chức năng mở rộng cho thiết lập setter & getter, đây là ví dụ của tôi Object.defineProperty (obj, name, 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 hàm toàn cục..Không có sẵn bên trong hàm khai báo đối tượng khác. Bạn sẽ phải sử dụng nó một cách tĩnh ...


0

Tóm lược:

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

Object.definePropertyđược sử dụng để tạo một thuộc tính mới trên đối tượng người chơi. Object.definePropertylà một hàm vốn có trong môi trường thời gian chạy JS và nhận các đối số sau:

Object.defineProperty(obj, prop, descriptor)

  1. Đối tượng mà chúng tôi muốn xác định một thuộc tính mới
  2. Các tên của thuộc tính mới , chúng tôi muốn xác định
  3. đối tượng mô tả

Đối tượng mô tả là phần thú vị. Ở đây chúng ta có thể định nghĩa những điều sau đây:

  1. cấu hình <boolean> : Nếu true mô tả thuộc tính có thể được thay đổi và thuộc tính có thể bị xóa khỏi đối tượng. Nếu cấu hình là falsethuộc tính mô tả được truyền vào Object.definePropertykhông thể thay đổi.
  2. Có thể ghi <boolean> : Nếu truethuộc tính có thể được ghi đè bằng toán tử gán.
  3. Có thể đếm được <boolean> : Nếu true thuộc tính có thể được lặp đi lặp lại trong một for...invòng lặp. Ngoài ra khi sử dụng Object.keyschức năng, phím sẽ có mặt. Nếu thuộc tính là falsechúng sẽ không bị lặp lại khi sử dụng for..invòng lặp và không hiển thị khi sử dụng Object.keys.
  4. get <function> : Một hàm được gọi bất cứ khi nào là thuộc tính được yêu cầu. Thay vì đưa ra giá trị trực tiếp, hàm này được gọi và giá trị trả về được đưa ra làm giá trị của thuộc tính
  5. set <function> : Một hàm được gọi bất cứ khi nào thuộc tính được gán. Thay vì đặt giá trị trực tiếp, hàm này được gọi và giá trị trả về được sử dụng để đặt giá trị của thuộc tính.

Thí dụ:

const player = {
  level: 10
};

Object.defineProperty(player, "health", {
  configurable: true,
  enumerable: false,
  get: function() {
    console.log('Inside the get function');
    return 10 + (player.level * 15);
  }
});

console.log(player.health);
// the get function is called and the return value is returned as a value

for (let prop in player) {
  console.log(prop);
  // only prop is logged here, health is not logged because is not an iterable property.
  // This is because we set the enumerable to false when defining the property
}


0

import { CSSProperties } from 'react'
import { BLACK, BLUE, GREY_DARK, WHITE } from '../colours'

export const COLOR_ACCENT = BLUE
export const COLOR_DEFAULT = BLACK
export const FAMILY = "'Segoe UI', sans-serif"
export const SIZE_LARGE = '26px'
export const SIZE_MEDIUM = '20px'
export const WEIGHT = 400

type Font = {
  color: string,
  size: string,
  accent: Font,
  default: Font,
  light: Font,
  neutral: Font,
  xsmall: Font,
  small: Font,
  medium: Font,
  large: Font,
  xlarge: Font,
  xxlarge: Font
} & (() => CSSProperties)

function font (this: Font): CSSProperties {
  const css = {
    color: this.color,
    fontFamily: FAMILY,
    fontSize: this.size,
    fontWeight: WEIGHT
  }
  delete this.color
  delete this.size
  return css
}

const dp = (type: 'color' | 'size', name: string, value: string) => {
  Object.defineProperty(font, name, { get () {
    this[type] = value
    return this
  }})
}

dp('color', 'accent', COLOR_ACCENT)
dp('color', 'default', COLOR_DEFAULT)
dp('color', 'light', COLOR_LIGHT)
dp('color', 'neutral', COLOR_NEUTRAL)
dp('size', 'xsmall', SIZE_XSMALL)
dp('size', 'small', SIZE_SMALL)
dp('size', 'medium', SIZE_MEDIUM)

export default font as Font


0

Xác định một thuộc tính mới trực tiếp trên một đối tượng hoặc sửa đổi một thuộc tính hiện có trên một đối tượng và trả về đối tượng.

Lưu ý: Bạn gọi phương thức này trực tiếp trên hàm tạo Object chứ không phải trên thể hiện của kiểu Object.

   const object1 = {};
   Object.defineProperty(object1, 'property1', {
      value: 42,
      writable: false, //If its false can't modify value using equal symbol
      enumerable: false, // If its false can't able to get value in Object.keys and for in loop
      configurable: false //if its false, can't able to modify value using defineproperty while writable in false
   });

nhập mô tả hình ảnh ở đây

Giải thích đơn giản về định nghĩa Tài sản.

Mã ví dụ: https://jsfiddle.net/manoj_antony32/pu5n61fs/


0

Object.defineProperty(Array.prototype, "last", {
  get: function() {
    if (this[this.length -1] == undefined) { return [] }
    else { return this[this.length -1] }
  }
});

console.log([1,2,3,4].last) //returns 4

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.