如何使用javascript Object.defineProperty

2022-08-30 01:39:36

我四处寻找如何使用Object.defineProperty方法,但找不到任何像样的东西。

有人给了我这段代码

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

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


答案 1

既然你问了类似的问题,让我们一步一步来。它有点长,但它可能比我花在写这篇文章上节省的时间要多得多:

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

然后,您可以更改(访问器)和(赋值器)方法。问题是大多数成员的行为都像公共变量一样,只是折扣在这里需要特别注意。但是,良好的设计需要封装每个数据成员,以保持代码的可扩展性。因此,您需要添加大量不执行任何操作的代码。这也是一个糟糕的设计,一个样板反模式。有时你不能只是将字段重构为方法(eshop代码可能会变得很大,或者一些第三方代码可能依赖于旧版本),所以样板在这里是较小的邪恶。但是,它仍然是邪恶的。这就是属性被引入多种语言的原因。您可以保留原始代码,只需将折扣成员转换为具有和块的属性:getDiscountsetDiscountgetset

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 必须是一个函数;其返回值用于读取属性;如果未指定,则默认值为未定义,其行为类似于返回未定义的函数
    • set 必须是函数;其参数在为属性赋值时用 RHS 填充;如果未指定,则默认值为未定义,其行为类似于空函数
  • 数据描述符 = 值 + 可写(请参阅下面的示例)
    • 默认值未定义;如果可写可配置可枚举(见下文)为真,则该属性的行为类似于普通数据字段
    • 可写 - 默认为假;如果不是 true,则该属性为只读;尝试写入将被忽略,而不会出现错误*!

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

  • 可配置 - 默认为假;如果不是 true,则无法删除该属性;尝试删除将被忽略,而不会出现错误*!
  • 可枚举 - 默认为假;如果为 true,它将在 ; 中迭代。如果为假,则不会对其进行迭代,但仍可作为公共访问for(var i in theObject)

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

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

如果你只写了几行有趣的话,你就不需要为此烦恼。但是,如果你想编写一个游戏(正如你在链接的问题中提到的),你应该关心好的设计。尝试在谷歌上搜索一些关于反模式代码气味的东西。它将帮助您避免诸如“哦,我需要再次完全重写我的代码!”之类的情况,如果您想编写大量代码,它可以为您节省数月的绝望。祝你好运。


答案 2

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

console.log(player.health);

它实际上与以下方面没有太大区别:

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

设置了 get 的反义词,在赋值时将使用 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