我经常看到这种模式来定义javascript对象
function Person(name) {
this.name = name;
}
Person.prototype.describe = function () {
return "Person called "+this.name;
};
在this article中,它表示直接向原型对象添加属性被视为反模式。
来自“基于经典类”的语言,除了方法之外必须定义属性听起来不太正确,不过在javascript中,方法应该只是具有函数值的属性(我在这里吗?)
我想知道是否有人可以解释这一点,或者甚至建议一种更好的方法来处理这些情况
答案 0 :(得分:6)
在通常的面向对象语言中,您有一个描述成员,方法和构造函数的类的定义。
在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
的所有实例都会收到__proto__
指针(称为Person.prototype
且大多数浏览器中的程序员无法访问)。所以,如果你打电话
var myPerson = new Person();
myPerson.describe();
它可以工作,因为JS直接在对象中查找对象成员,然后在其原型等中直到Object.prototype
。
关键是在第二种情况下,只存在一个函数实例。你可能会认为这是一个更好的设计。即使你不这样做,也只需要更少的记忆。
答案 1 :(得分:6)
该代码没有任何问题。据说这就是:
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
)中分配的属性特定于实例。
答案 2 :(得分:3)
正如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);
};
总结一下:
答案 3 :(得分:1)
就像 arxanas 在评论中所写的那样。原型中的数据属性或多或少类似于传统oop中的类级变量。除非您有特殊需要,否则这些不会每天使用。就是这样。
答案 4 :(得分:1)
在原型上声明属性根本不是反模式。当我查看prototype
对象时,我认为&#34;这就是此类型的原型对象对数据和方法的作用。&#34;
其他人警告不要在原型中为属性提供参考值,例如:Foo.prototype.bar = [];
---因为数组和对象是引用类型。引用类型是不可变的,因此&#34;类的所有实例都是不可变的。指的是相同的数组或对象。只需在原型中将它们设置为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
属性的信息嵌入在构造函数中。与#34;反模式&#34;形成鲜明对比。在原型中包含这些属性:
/**
* 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
对象赋予生命的业务。
原型拥有一切,并且是关于&#34;原型&#34;的原始信息来源。 Point
对象包含和两种方法。
我认为 not 包括原型中的数据属性是反模式。
答案 5 :(得分:1)
可能与之相关:通常,将您不拥有的对象修改为反模式。
意思是,如果您没有创建对象,那么您就不会“拥有”该对象。包括:
window
)