我四处寻找如何使用该Object.defineProperty方法,但找不到任何像样的东西。
Object.defineProperty
有人给了我这段代码:
Object.defineProperty(player, "health", { get: function () { return 10 + ( player.level * 15 ); } })
但我不明白。主要是,这get是我无法得到的(双关语)。它是如何工作的?
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块:
getDiscount
setDiscount
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 值 并在同一属性上 设置):
两个描述符都可以有以下成员:
可枚举 - 默认 false ;如果为真,它将被迭代for(var i in theObject);如果为 false,则不会被迭代,但仍可作为公共访问
for(var i in theObject)
除非在严格模式下- 在这种情况下,JS 会使用 TypeError 停止执行,除非它被try-catch 块捕获
要阅读这些设置,请使用Object.getOwnPropertyDescriptor().
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
Object.isFrozen(<yourObject>)
如果你只写几行有趣的东西,你就不需要为此烦恼。但是,如果您想编写游戏代码(正如您在链接问题中提到的那样),您应该关心良好的设计。尝试在 Google 上搜索有关 反模式 和 代码异味 的信息。它将帮助您避免诸如 “哦,我需要再次完全重写我的代码!”之类的情况。 ,如果您想大量编写代码,它可以为您节省数月的绝望。祝你好运。