如何使用javascript Object.defineProperty

Mat*_*ler 167 javascript object defineproperty

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

有人给了我这段代码:

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

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

Jan*_*roň 469

既然你问过类似的问题,那就让我们一步一步来.它有点长,但它可以比我写这篇文章花费更多的时间:

属性是一种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}
Run Code Online (Sandbox Code Playgroud)

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

function badProduct(obj) { obj.discount+= 20; ... }
function generalDiscount(obj) { obj.discount+= 10; ... }
function distributorDiscount(obj) { obj.discount+= 15; ... }
Run Code Online (Sandbox Code Playgroud)

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

if(obj.discount>80) obj.discount = 80;
Run Code Online (Sandbox Code Playgroud)

然后电子商店所有者可以进一步改变他的策略,例如"如果客户是经销商,最大折扣可以是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; } 
}
Run Code Online (Sandbox Code Playgroud)

然后你可以改变getDiscount(访问器)和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
Run Code Online (Sandbox Code Playgroud)

注意最后一行但是:正确折扣值的责任从客户代码(电子商店定义)移到产品定义.该产品负责保持其数据成员的一致性.如果代码与我们的想法一样工作,那么好的设计(粗略地说).

很多关于物业.但是javascript不同于像C#这样的纯面向对象语言,并且对功能进行不同的编码:

在C#中,将字段转换为属性是一个重大变化,因此如果您的代码可能在分开编译的客户端中使用,则公共字段应编码为自动实现的属性.

在Javascript中,标准属性(具有上述getter和setter的数据成员)由访问者描述符(在您的问题中的链接中)定义.独家地,您可以使用数据描述符(因此您不能使用ie 设置在同一属性上):

  • accessor descriptor = get + set(参见上面的例子)
    • 得到必须是一个功能; 其返回值用于阅读财产; 如果未指定,则默认值为 undefined,其行为类似于返回undefined的函数
    • set必须是一个函数; 在为属性赋值时,其参数由RHS填充; 如果未指定,则默认值为 undefined,其行为类似于空函数
  • data descriptor = value + writable(参见下面的例子)
    • value default undefined ; 如果可写,配置可枚举(见下文)为真,则该属性的行为类似于普通数据字段
    • 可写 - 默认为 false ; 如果不是真的,该财产是只读的; 尝试写入被忽略而没有错误*!

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

  • 可配置 - 默认为 false ; 如果不是,则不能删除该属性; 忽略尝试删除没有错误*!
  • enumerable - 默认为 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
Run Code Online (Sandbox Code Playgroud)

如果您不希望允许客户端代码这样的作弊,您可以通过三级限制来限制对象:

  • Object.preventExtensions(yourObject)可防止将新属性添加到 yourObject.使用Object.isExtensible(<yourObject>)检查对象上使用的方法.预防很(见下文).
  • Object.seal(yourObject)与上面相同,并且无法删除属性(有效地设置configurable: false为所有属性).使用Object.isSealed(<yourObject>)检测的物体此功能.密封(见下文).
  • Object.freeze(yourObject)与上面相同,属性无法更改(有效地设置writable: false为具有数据描述符的所有属性).Setter的可写属性不受影响(因为它没有).冻结是浅的:这意味着如果属性是Object,它的属性不会被冻结(如果你愿意,你应该执行像"深度冻结"之类的东西,类似于深层复制 - 克隆).使用Object.isFrozen(<yourObject>)检测到它.

如果你只是写几行乐趣,你不需要为此烦恼.但是如果你想编写游戏代码(正如你在链接问题中提到的那样),你应该真的关心好的设计.尝试google关于反模式代码气味的东西.它将帮助您避免诸如"哦,我需要再次完全重写代码!"之类的情况.如果你想要编写很多代码,它可以为你节省数月的绝望.祝好运.


Pau*_*aul 24

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

console.log(player.health);
Run Code Online (Sandbox Code Playgroud)

它实际上并没有太大的不同:

player.getHealth = function(){
  return 10 + this.level*15;
}
console.log(player.getHealth());
Run Code Online (Sandbox Code Playgroud)

设置了与get相反的值,当您赋值时将使用该值.由于没有setter,似乎不打算分配给玩家的健康:

player.health = 5; // Doesn't do anything, since there is no set function defined
Run Code Online (Sandbox Code Playgroud)

一个非常简单的例子:

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
Run Code Online (Sandbox Code Playgroud)


Cod*_*-EZ 10

defineProperty是Object上的一个方法,它允许您配置属性以满足某些标准.下面是一个简单的示例,其中employee对象具有两个属性firstName和lastName,并通过覆盖对象上的toString方法来追加这两个属性.

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

您将获得输出为: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());
Run Code Online (Sandbox Code Playgroud)

第一个参数是对象的名称,然后第二个参数是我们要添加的属性的名称,在我们的例子中它是toString,然后最后一个参数是json对象,其值为一个函数,三个参数可写,可枚举和configurable.Right现在我只是声明一切都是真的.

如果您运行该示例,您将获得输出为:Jameel Moideen

让我们理解为什么我们需要三个属性,如可写,可枚举和可配置. 可写 如果你将toString属性更改为其他东西,那么javascript的一个非常烦人的部分是 在此输入图像描述

如果再次运行,一切都会中断让我们将可写更改为false.如果再次运行相同,您将得到正确的输出为'Jameel Moideen'.此属性将防止稍后覆盖此属性. 如果您打印对象内的所有键,则可以枚举,您可以看到所有属性,包括toString.

console.log(Object.keys(employee));
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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

如果有人稍后重新定义了该对象,例如枚举为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));
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

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

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