为什么在原型中定义属性被视为反模式
Why defining properties in the prototype is considered an antipattern
我经常看到这种模式来定义javascript对象
function Person(name) {
this.name = name;
}
Person.prototype.describe = function () {
return "Person called "+this.name;
};
在本文中,它说将属性直接添加到原型 objct 被认为是一种反模式。
来自"基于经典类"的语言,除了方法之外,必须定义属性听起来不太正确,而且在javascript中,方法应该只是一个具有函数值的属性(我在这里吗?
我想知道是否有人可以解释这一点,甚至提出更好的方法来处理这些情况
在通常的面向对象语言中,您有一个描述成员、方法和构造函数的类的定义。
在JS中,"类"的定义(它不像其他语言那样是真正的类......有时使用术语伪类)是构造函数本身。如果你的对象被name
参数化,写是有意义的
function Person(name) {
this.name = name;
}
即必须在构造函数中设置属性name
。
当然,你可以写
function Person(name) {
this.name = name;
this.describe = function() { ... };
}
它将按您的预期工作。
但是,在本例中,您将使用构造函数的每个调用创建该方法的单独实例。
另一方面,在这里:
Person.prototype.describe = function () {
return "Person called "+this.name;
};
只需定义一次方法。Person
的所有实例都将收到一个指向Person.prototype
的指针(称为__proto__
,程序员在大多数浏览器中无法访问)。所以如果你打电话
var myPerson = new Person();
myPerson.describe();
它会起作用,因为 JS 直接在对象中查看对象成员,然后在其原型等中一直到 Object.prototype
.
关键是,在第二种情况下,函数只有一个实例存在。您可能会同意这是一个更好的设计。即使你不这样做,它也只是需要更少的内存。
该代码没有错。据说这意味着:
function Person(name) {
this.name = name;
}
Person.prototype.age = 15; //<= adding a hardcoded property to the prototype
现在你会看到这个:
var pete = new Person('Pete'), mary = new Person('Mary');
pete.age; //=> 15
mary.age //=> 15
大多数时候,这不是你想要的。分配给构造函数原型的属性在所有实例之间共享,构造函数(this.name
)内分配的属性特定于实例。
正如arxanas所说,这篇文章提到了数据属性。
我认为原因是数据通常特定于实例,因此将其添加到原型中没有意义。
此外,如果你的数据是可变类型,例如数组,并且你将其分配给原型,那么这个数组实例在所有实例之间共享,你不能像每个实例都有自己的数组一样使用它。
示例:以下情况会导致不正确的行为:
function Set() {
}
// shared between instances
// each instance adds values to **the same** array
Set.prototype.elements = [];
Set.prototype.add = function(x) {
this.elements.push(x);
};
它应该是:
function Set() {
// each instance gets its own array
this.elements = [];
}
Set.prototype.add = function(x) {
this.elements.push(x);
};
总结一下:
- 将应在所有实例之间共享的属性添加到原型。
- 在构造函数中分配特定于实例的数据。
就像arxanas在他的评论中写道。原型中的数据属性或多或少类似于传统 oop 中的类级变量。除非您有非常特殊的需求,否则这些不会每天使用。就这样。
在原型上声明属性根本不是反模式。当我查看一个prototype
对象时,我认为"这就是这种类型的原型对象对数据和方法的作用。
其他人警告不要在原型中为属性提供参考值,例如:Foo.prototype.bar = [];
---因为数组和对象是引用类型。引用类型是不可变的,因此"类"的每个实例都引用相同的数组或对象。只需将它们设置为在原型中null
,然后在构造函数中为它们提供一个值。
我总是在原型中包含所有属性,原因非常明确:向其他程序员传达哪些属性是公开可用的,以及它们的默认值是什么,而无需他们筛选构造函数来弄清楚。
如果要创建需要文档的共享库,这将特别有用。
请考虑以下示例:
/**
* class Point
*
* A simple X-Y coordinate class
*
* new Point(x, y)
* - x (Number): X coordinate
* - y (Number): Y coordinate
*
* Creates a new Point object
**/
function Point(x, y) {
/**
* Point#x -> Number
*
* The X or horizontal coordinate
**/
this.x = x;
/**
* Point#y -> Number
*
* The Y or vertical coordinate
**/
this.y = y;
}
Point.prototype = {
constructor: Point,
/**
* Point#isAbove(other) -> bool
* - other (Point): The point to compare this to
*
* Checks to see if this point is above another
**/
isAbove: function(other) {
return this.y > other.y;
}
};
(文档格式:PDoc)
在这里,仅仅阅读文档有点尴尬,因为有关x
和y
属性的信息嵌入在构造函数中。与在原型中包含这些属性的"反模式"形成对比:
/**
* class Point
*
* A simple X-Y coordinate class
*
* new Point(x, y)
* - x (Number): X coordinate
* - y (Number): Y coordinate
*
* Creates a new Point object
**/
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype = {
/**
* Point#x -> Number
*
* The X or horizontal coordinate
**/
x: 0,
/**
* Point#y -> Number
*
* The Y or vertical coordinate
**/
y: 0,
constructor: Point,
/**
* Point#isAbove(other) -> bool
* - other (Point): The point to compare this to
*
* Checks to see if this point is above another
**/
isAbove: function(other) {
return this.y > other.y;
}
};
现在查看原型可以为您提供实际对象的快照,这更容易在脑海中可视化,并且作者更容易编写文档。构造函数也没有被文档弄乱,而是坚持将Point
对象带入生活的业务。
拥有一切,并且是有关"原型"Point
对象对方法和数据具有什么的规范信息源。
我认为在原型中不包括数据属性是反模式。
可能相关:通常,修改您不拥有的对象被视为反模式。
这意味着,如果您没有创建对象,那么您就不"拥有"该对象。包括:
- 本机对象(对象、数组等)
- DOM 对象
- 浏览器对象模型 (BOM) 对象(如
window
) - 库对象
Source Maintainable Javascript by Nicholas C. Zakas
- 数据属性仅在切换设备模式下工作
- PhoneGap 2.0(Cordova)DatePicker插件'模式'-属性以获取日期和时间
- 如何在javascript对象中设置属性的类型,就像mongoose模式设计一样
- 为什么在原型中定义属性被视为反模式
- 为什么除了html5输入模式属性之外,这个简单的regex在任何地方都能工作
- 输入元素模式属性的Javascript正则表达式在reFiddle上有效,但在页面上无效
- 对象绑定模式的rest属性应该是最后一个
- JS:两个或多个非严格模式下具有相同名称的对象属性
- 从引导模式窗口中删除模式属性
- 为什么输入模式属性不适用于数字
- 如何在文本框上使用模式属性
- 如何使用HTML5使用模式属性仅验证CSV文件上传
- 输入元素模式属性的行为不象regex
- 输入模式属性以更改输入框的颜色
- 如何在Mongoose中将模式属性设置为SubDocument类型
- 适当模拟“模式”属性
- 使用锚点作为提交按钮会导致模式属性无法验证
- 切换对话框的“模式”属性打开时
- 使用websockets激活HTML5模式属性
- Angular中的模式属性(硬编码版本可以,动态版本不行)