如何使用javascript Object.defineProperty


183

我环顾四周如何使用该Object.defineProperty方法,但找不到任何合适的方法。

有人给了我这段代码

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

但是我不明白。主要是,这get是我无法得到的(双关语意)。它是如何工作的?


Answers:


498

由于您提出了类似的问题,所以让我们逐步进行。它更长一些,但是它可以为您节省比我写这篇文章更多的时间:

属性是一种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}

然后,在您的客户代码(电子商店)中,您可以为产品添加折扣:

function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }

后来,电子商店的老板可能会意识到折扣不能超过80%。现在,您需要在客户代码中查找每一次出现的折扣修改,并添加一行

if(obj.discount>80) obj.discount = 80;

然后,电子商店所有者可以进一步更改其策略,例如“如果客户是转售商,则最大折扣可以是90%”。而且,您需要在多个位置上再次进行更改,并且还需要记住,只要更改策略,就必须更改这些行。这是一个糟糕的设计。这就是为什么封装是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; } 
}

然后,您可以更改getDiscountaccessor)和setDiscountmutator)方法。问题在于大多数成员的行为都像公共变量,只是折扣在这里需要特别注意。但是良好的设计要求封装每个数据成员以保持代码可扩展。因此,您需要添加大量不执行任何操作的代码。这也是一个糟糕的设计,一个样板反模式。有时,您不能稍后再将字段重构为方法(eshop代码可能会变大,或者某些第三方代码可能取决于旧版本),因此,此处的样板代码的危害性较小。但是,这仍然是邪恶的。这就是为什么将属性引入许多语言的原因。您可以保留原始代码,只需将Discount Member转换为getset块:

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

请注意最后一行,但只有一行:正确折扣值的责任已从客户代码(电子商店定义)移至产品定义。该产品负责保持其数据成员的一致性。如果代码的工作方式与我们的想法相同,则可以说是好的设计。

关于属性的那么多。但是javascript与纯粹的面向对象语言(如C#)不同,并且对功能进行了不同的编码:

在C#中,将字段转换为属性是一项重大更改,因此,如果您的代码可能在单独编译的客户端中使用,则应将公共字段编码为“ 自动实现的属性”

在Javascript中,标准属性(上述具有getter和setter的数据成员)由访问器描述符(在您所关注的链接中)定义。独家,您可以用数据描述符(所以你不能使用即设定在相同的属性):

  • 访问者描述符 = get + set(请参见上面的示例)
    • get必须是一个函数;它的返回值用于读取属性;如果未指定,则默认值为 undefined,其行为类似于返回undefined的函数
    • set必须是一个函数;在为属性分配值时,其参数由RHS填充;如果未指定,则默认值为 undefined,其行为类似于一个空函数
  • 数据描述符 =值+可写(请参见下面的示例)
    • 默认未定义 ; 如果 writable configurable enumerable(请参见下文)为true,则该属性的行为类似于普通的数据字段
    • 可写 -默认为 false ; 如果不是 true,则该属性为只读;尝试写操作将被忽略,没有错误*!

这两个描述符都可以具有以下成员:

  • 可配置 -默认为 false ; 如果不是,则该属性不能删除;尝试删除将被忽略,没有错误*!
  • 枚举默认值 false ; 如果为true,则会在中进行迭代for(var i in theObject);如果为false,则不会进行迭代,但仍可以作为公共访问

*除非在严格模式下 -除非在try-catch块中被捕获,否则在这种情况下JS会停止使用TypeError执行

要阅读这些设置,请使用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)防止将新属性添加到 yourObjectObject.isExtensible(<yourObject>)用于检查对象是否使用了该方法。预防措施较浅(请参阅下文)。
  • 与上面的 Object.seal(yourObject)相同,并且无法删除属性(有效地设置configurable: false为所有属性)。使用Object.isSealed(<yourObject>)检测的物体此功能。密封垫较浅(请参阅下文)。
  • 与上面的 Object.freeze(yourObject)相同,并且属性不能更改(有效地设置writable: false为带有数据描述符的所有属性)。设置器的可写属性不受影响(因为它没有一个)。冻结很:这意味着如果属性为Object,则不会冻结其属性(如果您愿意,则应执行“深度冻结”之类的操作,类似于深度复制-克隆)。使用Object.isFrozen(<yourObject>)检测到它。

如果您仅写几行有趣的内容,则无需理会。但是,如果您想编写游戏代码(如在链接的问题中提到的那样),则应该真正关心良好的设计。尝试搜索有关反模式代码气味的内容。它将帮助您避免出现诸如“哦,我需要再次完全重写我的代码!”之类的情况。,如果您想编写很多代码,可以节省数月的绝望。祝好运。


这部分很清楚。 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

get是一个在您尝试读取值时调用的函数player.health,例如:

console.log(player.health);

实际上,它与以下内容没有太大区别:

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

设置了相反的get,将在您分配给该值时使用。由于没有二传手,因此似乎无意分配玩家的健康状况:

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

一个非常简单的例子:

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


就像一个函数,您实际上不需要使用它()来调用...我不明白他们发明这种东西时的想法。功能是完全一样的:jsbin.com/bugipi/edit?js,console,output
VSYNC

15

defineProperty是Object上的一种方法,可让您配置属性以满足某些条件。这是一个简单的示例,该雇员对象具有两个属性firstName和lastName,并通过覆盖对象上的toString方法来追加两个属性。

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

您将得到输出为:Jameel Moideen

我将通过在对象上使用defineProperty来更改相同的代码

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());

第一个参数是对象的名称,第二个参数是我们要添加的属性的名称,在本例中是toString,最后一个参数是json对象,其值将是一个函数,并且三个参数可写,可枚举并且可配置。现在,我刚刚将所有内容声明为true。

如果您运行示例,您将获得Output as:Jameel Moideen

让我们了解为什么我们需要三个属性,例如可写,可枚举和可配置。

可写的

javascript中最令人讨厌的部分之一是,例如,如果将toString属性更改为其他内容,例如

在此处输入图片说明

如果再次运行,一切都会中断。让我们将可写更改为false。如果再次运行相同,您将获得正确的输出为'Jameel Moideen'。此属性将防止以后覆盖此属性。

数不清的

如果在对象内打印所有键,则可以看到所有属性,包括toString。

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

在此处输入图片说明

如果将enumerable设置为false,则可以对其他所有人隐藏toString属性。如果再次运行,您将得到名字,姓氏

可配置的

如果以后有人重新定义该对象(例如,可枚举为true并运行它)。您可以看到toString属性再次出现。

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));

在此处输入图片说明

您可以通过将configurable设置为false来限制此行为。

此信息的原始引用来自我的个人博客


1
我知道您在博客上拥有此功能,只是将其粘贴到此处,但至少将来会知道:屏幕截图在SO上并不流行。您无法复制粘贴代码进行尝试,并且搜索引擎或辅助技术都不会看到该代码。
多米诺骨牌

@JacqueGoupil你是对的。我将通过添加代码而不是屏幕截图进行更新
Code-EZ

3

基本上,这defineProperty是一种接受3个参数的方法-一个对象,一个属性和一个描述符。在此特定调用中发生的是"health",将player对象的属性分配给该播放器对象级别的10倍和15倍。


0

是的,没有更多的功能可以扩展设置设置器和获取器,这是我的示例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()是全局函数。否则在声明对象的函数内部不可用。您必须静态使用它。


0

摘要:

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

Object.defineProperty用于在播放器对象上创建新属性。Object.defineProperty是JS运行时环境中固有的函数,并采用以下参数:

Object.defineProperty(obj, prop, descriptor)

  1. 对象上,我们要定义一个新的属性
  2. 我们要定义的新属性名称
  3. 描述符对象

描述符对象是有趣的部分。在这里,我们可以定义以下内容:

  1. 可配置的 <boolean>:如果true 可以更改属性描述符,并且可以从对象中删除属性。如果是可配置false的,Object.defineProperty则不能更改传入的描述符属性。
  2. 可写 <boolean>:如果true属性可以使用赋值运算符覆盖。
  3. Enumerable <boolean>:如果true 可以在for...in循环中迭代该属性。同样,使用该Object.keys功能时,也会显示该键。如果属性为false,则不会使用for..in循环来迭代它们,并且使用时不会显示它们Object.keys
  4. get <function>:在需要该属性时会调用的函数。而不是给出直接值,而是调用此函数,并将返回值作为属性的值给出
  5. set <function>:每当分配属性时都会调用的函数。代替设置直接值,此函数被调用,并且返回的值用于设置属性的值。

例:

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

直接在对象上定义新属性,或在对象上修改现有属性,然后返回对象。

注意:您可以直接在Object构造函数上而不是在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
   });

在此处输入图片说明

关于定义属性的简单说明。

示例代码:https : //jsfiddle.net/manoj_antony32/pu5n61fs/


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.