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

Why defining properties in the prototype is considered an antipattern

本文关键字:模式 属性 原型 定义 为什么      更新时间:2023-09-26

我经常看到这种模式来定义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)

在这里,仅仅阅读文档有点尴尬,因为有关xy属性的信息嵌入在构造函数中。与在原型中包含这些属性的"反模式"形成对比:

/**
 * 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