为什么在原型中定义属性被认为是反模式

时间:2012-08-10 14:52:44

标签: javascript prototype anti-patterns prototype-programming prototypal-inheritance

我经常看到这种模式来定义javascript对象

function Person(name) {
    this.name = name;
}
Person.prototype.describe = function () {
    return "Person called "+this.name;
};

this article中,它表示直接向原型对象添加属性被视为反模式。

来自“基于经典类”的语言,除了方法之外必须定义属性听起来不太正确,不过在javascript中,方法应该只是具有函数值的属性(我在这里吗?)

我想知道是否有人可以解释这一点,或者甚至建议一种更好的方法来处理这些情况

6 个答案:

答案 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

在这里阅读文档有点尴尬,因为有关xy属性的信息嵌入在构造函数中。与#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)

可能与之相关:通常,将您不拥有的对象修改为反模式

意思是,如果您没有创建对象,那么您就不会“拥有”该对象。包括:

  • 本机对象(对象,数组等)
  • DOM对象
  • 浏览器对象模型(BOM)对象(例如window
  • 库对象

来源Maintainable Javascript by Nicholas C. Zakas