如何使用 javascript Object.defineProperty

IT技术 javascript object defineproperty
2021-03-08 10:27:06

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

有人给了我这段代码

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

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

6个回答

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

属性是一种 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,其行为类似于空函数
  • 数据描述符= 值 + 可写(见下面的例子)
    • 默认未定义如果writable configureenumerable(见下文)为真,则该属性的行为就像一个普通的数据字段
    • 可写- 默认为false如果不是true,则该属性是只读的;尝试写入被忽略,没有错误*!

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

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

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

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

这部分很清楚。 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
2021-05-02 10:27:06

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

console.log(player.health);

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

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

get 的反面是 set,它会在您分配给值时使用。由于没有设置器,似乎不打算分配给玩家的健康:

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
2021-05-08 10:27:06

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 对象,它的值将是一个函数和三个参数 writable,enumerable和可配置的。现在我只是宣布一切都是真的。

如果你运行这个例子,你会得到输出:Jameel Moideen

让我们来理解为什么我们需要writable、enumerable 和 configure三个属性

可写

javascript 中非常烦人的部分之一是,如果您将 toString 属性更改为其他内容,例如

在此处输入图片说明

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

可枚举的

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

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

在此处输入图片说明

如果将 enumerable 设置为 false ,则可以对其他人隐藏 toString 属性。如果再次运行,您将获得 firstName,lastName

可配置

如果稍后有人重新定义了对象,例如可枚举为真并运行它。可以看到 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));

在此处输入图片说明

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

此信息的原始参考来自我的个人博客

我知道你在你的博客上有这个,只是把它贴在这里,但至少在未来知道这一点:屏幕截图在 SO 上并不流行。您无法复制粘贴代码来尝试,搜索引擎或辅助技术也不会看到该代码。
2021-05-04 10:27:06
@JacqueGoupil 你是对的。我将通过添加代码而不是屏幕截图来更新
2021-05-21 10:27:06

基本上,defineProperty是一个接受 3 个参数的方法 - 一个对象、一个属性和一个描述符。在这个特定的调用中发生的事情是对象"health"属性player被分配给玩家对象级别的 10 加 15 倍。

是的,不再为 setup setter & getter 扩展功能,这是我的示例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);