小编典典

如何使用javascript Object.defineProperty

all

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

有人给了我这段代码:

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

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


阅读 69

收藏
2022-07-29

共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; } 
}

然后你可以改变getDiscount( accessor ) 和setDiscount( mutator )
方法。问题是大多数成员的行为都像公共变量,只是折扣在这里需要特别注意。但是好的设计需要封装每个数据成员以保持代码的可扩展性。所以你需要添加很多什么都不做的代码。这也是一个糟糕的设计,一个
样板的反模式 。有时您不能只是稍后将字段重构为方法(eshop
代码可能会变大,或者某些第三方代码可能依赖于旧版本),因此这里的样板文件不那么邪恶。但是,它仍然是邪恶的。这就是为什么将属性引入许多语言的原因。您可以保留原始代码,只需将折扣成员转换为属性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 的数据成员)由 访问器描述符
(在您的问题中的链接中)定义。排他地,您可以使用 数据描述符 (因此您不能使用 ie 并在同一属性上 设置):

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

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

  • 可配置 - 默认 false ;如果不为真,则无法删除该属性;尝试删除被忽略,没有错误*!
  • 可枚举 - 默认 false ;如果为真,它将被迭代for(var i in theObject);如果为 false,则不会被迭代,但仍可作为公共访问

  • 除非在严格模式下- 在这种情况下,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(yourObject) 防止将新属性添加到 yourObject 。用于Object.isExtensible(<yourObject>)检查是否在对象上使用了该方法。预防很 (阅读下文)。
  • Object.seal(yourObject) 同上,属性不能被移除(有效设置configurable: false为所有属性)。用于Object.isSealed(<yourObject>)检测对象上的此特征。封印很 (见下文)。
  • Object.freeze(yourObject) 同上,属性不可更改(有效设置writable: false为所有带有数据描述符的属性)。Setter 的可写属性不受影响(因为它没有)。冻结是 的:这意味着如果属性是对象,它的属性不会被冻结(如果你愿意,你应该执行类似“深度冻结”的操作,类似于深度复制 - 克隆)。用来Object.isFrozen(<yourObject>)检测它。

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

2022-07-29