既然你问了similar question,那就一步步来吧。它有点长,但它可能会比我花在写这篇文章上的时间来节省你更多的时间:
Property 是一种 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#中,将字段转换为属性是breaking change,因此如果您的代码可能在单独编译的客户端中使用,则应将公共字段编码为Auto-Implemented Properties。
在 Javascript 中,标准属性(具有上述 getter 和 setter 的数据成员)由 访问器描述符 定义(在您问题中的链接中)。排他地,您可以使用 data descriptor(因此您不能在同一属性上使用即 value 和 set):
-
访问器描述符 = get + set(见上面的例子)
-
get 必须是函数;它的返回值用于读取属性;如果未指定,则默认为 undefined,其行为类似于返回 undefined 的函数
-
set 必须是函数;在给属性赋值时,它的参数用 RHS 填充;如果未指定,则默认为 undefined,其行为类似于空函数
-
数据描述符 = 值 + 可写(参见下面的示例)
-
值默认未定义;如果 writable、configurable 和 enumerable(见下文)为真,则该属性的行为类似于普通数据字段
-
可写 - 默认false;如果不是 true,则该属性是只读的;尝试写入被忽略,没有错误*!
两个描述符都可以有这些成员:
-
可配置 - 默认false;如果不为真,则无法删除该属性;尝试删除被忽略,没有错误*!
-
可枚举 - 默认false;如果为真,将在
for(var i in theObject) 中迭代;如果为 false,则不会被迭代,但仍可作为公共访问
* 除非在 strict mode - 在这种情况下 JS 会停止执行 TypeError 除非它被 try-catch block 捕获
要阅读这些设置,请使用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
如果你不想让客户端代码这样作弊,你可以通过三个级别的限制来限制对象:
如果你只写几行有趣的东西,你就不必为此烦恼。但是,如果您想编写游戏代码(正如您在链接问题中提到的那样),您应该关心良好的设计。尝试在 Google 上搜索有关 antipatterns 和 代码气味 的信息。它将帮助您避免像“哦,我需要再次完全重写我的代码!”这样的情况,如果您想大量编码,它可以为您节省数月的绝望。祝你好运。