如何使用javascript Object.defineProperty
我环顾四周如何使用Object.defineProperty
方法,但找不到像样的东西。
有人给我这个代码片段 :
Object.defineProperty(player, "health", { get: function () { return 10 + ( player.level * 15 ); } })
但我不明白。 主要是, get
是我不能得到(双关语意)。 它是如何工作的?
既然你问了一个类似的问题 ,让我们一步一步来。 时间稍长一些,但是可以为您节省更多的时间。
属性是一个OOPfunction,旨在清晰地分离客户端代码。 例如,在某个电子商店中,您可能有这样的对象:
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%。 现在您需要在客户端代码中find每次发生折扣修改并添加一行
if(obj.discount>80) obj.discount = 80;
那么网店老板可能会进一步改变自己的策略,比如“如果客户是经销商,最高折扣可以达到90%” 。 而且您需要再次在多个地方进行更改,而且您需要记住在战略发生变化时随时更改这些行。 这是一个糟糕的devise。 这就是封装是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 )方法。 问题是,大多数成员的行为像共同的变数,只是折扣需要特别照顾在这里。 但好的devise需要封装每个数据成员以保持代码的可扩展性。 所以你需要添加大量的什么都不做的代码。 这也是一个糟糕的devise,一个样板反模式 。 有时你不能只是稍后重构字段到方法(eshop代码可能会变大或者一些第三方代码可能取决于旧版本),所以这里的样板文件是较less的。 但是,它仍然是邪恶的。 这就是为什么财产被引入到许多语言。 您可以保留原始代码,只需将折扣成员转换为具有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
注意最后一行:正确折扣值的责任从客户代码(电子商店定义)转移到产品定义。 产品负责保持数据成员的一致性。 如果代码和我们的想法一样,好的devise是(粗略地说)。
太多关于属性。 但是,JavaScript与纯粹的面向对象语言(如C#)不同,并且对function进行了不同的编码:
在C#中 ,将字段转换为属性是一个突破性的变化 ,所以如果你的代码可能在分离编译的客户端中使用,公共字段应该被编码为自动实现的属性 。
在Javascript中 ,标准属性(上面描述的getter和setter的数据成员)由访问者描述符 (在您的问题中的链接中)定义。 唯一的,你可以使用数据描述符 (所以你不能使用ie的价值和设置在同一个属性):
- 访问器描述符 = get + set(参见上面的例子)
- 获得必须是一个function; 其返回值用于阅读财产; 如果未指定,则默认值是未定义的 ,其行为与返回未定义的函数相同
- 设置必须是一个function; 它的参数用RHS填充给属性赋值; 如果未指定,则缺省值是未定义的 ,其行为类似于空函数
- 数据描述符 =值+可写(见下面的例子)
- 值默认为undefined ; 如果可写 , 可 configuration和可枚举 (见下文)属实,则该属性的行为与普通数据字段相同
- 可写 – 默认为false ; 如果不是这样 ,该属性是只读的; 尝试写入被忽略而没有错误*!
这两个描述符都可以有这些成员:
- 可configuration – 默认为false ; 如果不是这样,则不能删除该属性; 试图删除被忽略而没有错误*!
- enumerable – 默认为false ; 如果为true,则会迭代(
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>)
在对象上检测此function。 封印很浅 (请看下面)。 - Object.freeze(yourObject)与上面相同,属性不能更改(有效地设置为
writable: false
为具有数据描述符的所有属性)。 Setter的可写属性不受影响(因为它没有)。 冻结是浅的 :这意味着如果这个属性是Object,它的属性不会被冻结(如果你愿意的话,你应该执行类似于“深度冻结”的操作,类似于深度复制 – 克隆 )。 使用Object.isFrozen(<yourObject>)
来检测它。
如果你只写了几行乐趣,你不需要打扰。 但是如果你想编写一个游戏(就像你在链接问题中提到的那样),你应该真的关心好的devise。 尝试谷歌有关反模式和代码气味的东西。 它会帮助你避免像“哦,我需要重新写我的代码! ,如果你想编码很多,它可以节省你几个月的绝望。 祝你好运。
get
是一个函数,当你尝试读取值player.health
时被调用,如下所示:
console.log(player.health);
它实际上没有太大的不同:
player.getHealth = function(){ return 10 + this.level*15; } console.log(player.getHealth());
get的相反位置被设置,当你赋值的时候会被使用。 由于没有二传手,似乎分配给运动员的健康不是:
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上的一个方法,它允许你configuration属性来满足一些标准。 下面是一个带有两个属性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对象,它有一个值将成为一个函数,三个参数可写,enumerable并可configuration。现在我刚刚宣布一切都是真实的。
如果你运行的例子,你会得到输出为: Jameel Moideen
让我们来了解为什么我们需要三个属性,如可写,可枚举和可configuration。 可写 javascript的一个非常恼人的部分是,如果你改变toString属性为其他例如
如果再次运行,所有事情都会中断让我们把可写为false。 如果再次运行相同,你将得到正确的输出“Jameel Moideen”。 此属性将防止稍后覆盖此属性。 枚举如果你打印对象内的所有键,你可以看到所有的属性,包括toString。
console.log(Object.keys(employee));
如果将enumerable设置为false,则可以隐藏其他人的toString属性。 如果再次运行,您将得到firstName,lastName可configuration
如果稍后有人重新定义了对象,例如enumerable为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));
您可以通过将可configuration设置为false来限制此行为。
这个信息的原始参考是从我的个人博客
基本上, defineProperty
是一个接受3个参数的方法 – 一个对象,一个属性和一个描述符。 在这个特定的调用中发生的事情是player
对象的"health"
属性被赋予10倍加15倍该玩家对象的等级。
是没有更多的function扩展设置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);
Object.defineProperty()是一个全局函数。它不能在声明对象的函数内部使用。您必须静态使用它…