我四处寻找如何使用该Object.defineProperty
方法,但找不到任何像样的东西。
有人给了我这段代码:
Object.defineProperty(player, "health", {
get: function () {
return 10 + ( player.level * 15 );
}
})
但我不明白。主要是,这get
是我无法得到的(双关语)。它是如何工作的?
我四处寻找如何使用该Object.defineProperty
方法,但找不到任何像样的东西。
有人给了我这段代码:
Object.defineProperty(player, "health", {
get: function () {
return 10 + ( player.level * 15 );
}
})
但我不明白。主要是,这get
是我无法得到的(双关语)。它是如何工作的?
既然你问了类似的问题,让我们一步一步来。它有点长,但它可能比我花在写这个上的时间节省更多的时间:
属性是一种 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 代码可能会变大或者一些第三方代码可能依赖于旧版本),所以这里的样板是不那么邪恶的。但是,它仍然是邪恶的。这就是为什么将属性引入许多语言的原因。您可以保留原始代码,只需将折扣会员转换为带有get
和set
块:
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值并在同一属性上设置):
两个描述符都可以具有以下成员:
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.isExtensible(<yourObject>)
用于检查对象是否使用了该方法。预防很浅(阅读下文)。configurable: false
为所有属性)。使用Object.isSealed(<yourObject>)
检测的物体此功能。密封很浅(阅读下文)。writable: false
为所有带数据描述符的属性)。Setter 的可写属性不受影响(因为它没有)。冻结是浅的:这意味着如果属性是对象,它的属性不会被冻结(如果你愿意,你应该执行类似“深度冻结”的操作,类似于深度复制克隆)。使用Object.isFrozen(<yourObject>)
检测到它。如果你只写几行有趣的,你就不需要为此烦恼。但是如果你想编写一个游戏(正如你在链接问题中提到的),你应该关心好的设计。尝试在 google 上搜索有关反模式和代码异味的内容。它将帮助您避免诸如“哦,我需要再次完全重写我的代码!”之类的情况。,如果您想大量编码,它可以为您节省数月的绝望。祝你好运。
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
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 来限制此行为。
基本上,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);