原型为什么定义特性被认为是一个反
我经常看到这种模式来定义JavaScript对象原型为什么定义特性被认为是一个反
function Person(name) {
this.name = name;
}
Person.prototype.describe = function() {
return "Person called "+this.name;
};
而且在this article它说,直接添加属性原型objct被认为是一个反模式。
来自“古典基于类”的语言,必须定义方法以外的属性听起来不太正确,更在JavaScript中,其中一个方法应该只是一个具有函数值的属性(我在这里吗? )
我想知道如果任何人都可以解释这一点,甚至提出一个更好的方式来处理这些情况
在通常的面向对象的语言中,您定义了描述成员,方法和构造函数的类。
在JS中,“类”的定义(它不像其他语言中的类一样...有时使用术语伪类)是构造函数本身。如果你的对象是由name
parametrised,是有意义的编写
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
的所有实例都会收到一个指针(__proto__
,大多数浏览器中的程序员都无法访问)到Person.prototype
。所以,如果你
var myPerson = new Person();
myPerson.describe();
它会工作,因为JS直接观察对象成员中的对象,然后在它的原型等,一路到Object.prototype
。
问题是,在第二种情况下,只有一个函数实例存在。你可能会同意这是一个更好的设计。即使你不这样做,它只需要更少的内存。
非常感谢,我没有意识到,从构造函数的每个方法定义将是一个不同的函数实例... – opensas 2012-08-10 15:17:46
由于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中的类级变量差不多。除非您有非常特殊的需求,否则这些服务不会每天使用。就这样。
该代码没有问题。推测这是什么意思:
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
)是特定于实例的属性。
在原型上声明属性根本不是反模式。当我看到一个对象时,我认为“这就是这种类型的原型对象对数据和方法的作用。”
其他人已经警告不要在原型中给属性一个参考值,例如: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
对象对于方法和数据具有什么样的信息的标准来源。
我会争辩说不是包括原型中的数据属性是反模式。
它说“数据属性”,我相信它是指变量。我不认为它提到了向原型添加功能的任何事情。 – 2012-08-10 14:54:54
arxanas说什么。确切的引用是:“一个类的主体只能包含方法,没有数据属性,具有数据属性的原型通常被认为是反模式,所以这只是一个最佳实践。”换句话说,通过“数据属性”,作者的意思是“不属于方法/功能的属性”。 – ruakh 2012-08-10 14:57:44
你的问题是基于一个误解,不知道该怎么做。我永远不会记得看到数据被放置在原型上。我不能真正编辑你的问题在原型中有数据,因为这不是我经常看到的*模式。 – Esailija 2012-08-10 14:58:22