Защо дефинирането на свойства в прототипа се счита за антипаттерн

Често виждам този модел за дефиниране на javascript обекти

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

И в тази статия се казва, че добавянето на свойства директно към прототипния обект се счита за анти- модел.

Идвайки от езици, базирани на класически клас, необходимостта да се дефинират свойствата освен методите не звучи съвсем правилно, освен това в javascript, където методът трябва да бъде просто свойство с функционална стойност (прав ли съм тук?)

Исках да знам дали някой може да обясни това или дори да предложи по-добър начин за справяне с тези ситуации


comment
Казва свойства на данните, което според мен се отнася до променливи. Не мисля, че се споменава нещо за добавяне на функции към прототип.   -  person Waleed Khan    schedule 10.08.2012
comment
Какво каза arxanas. Точният цитат е, тялото на клас може да съдържа само методи, без свойства на данни. Прототипите със свойства на данни обикновено се считат за анти-модел, така че това просто налага най-добрата практика. С други думи, под свойства на данни авторът има предвид свойства, които не са методи/функции.   -  person ruakh    schedule 10.08.2012
comment
Вашият въпрос се основава на недоразумение, не знам какво да правя. Аз също не си спомням да съм виждал данни да бъдат поставяни в прототипа. Наистина не мога да редактирам въпроса ви, за да имам данни в прототипа, защото това не е модел, често виждам.   -  person Esailija    schedule 10.08.2012
comment
Интересувам се защо използването на нефункционални свойства се счита за анти-модел. Има очевиден проблем със свойство на обект, което вероятно е неволно споделено между екземпляри, но поставянето на примитивно свойство в прототипа е ефективен начин за предоставяне на стойност по подразбиране за свойството.   -  person Tim Down    schedule 10.08.2012
comment
Здравей, Тим, Ако разбирам правилно отговорите, които ми бяха дадени тук, ако поставиш примитивно свойство на прототип, всеки екземпляр ще споделя същия този примитив (вид статичен член в java)... като променлива на клас.   -  person opensas    schedule 11.08.2012


Отговори (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.

Въпросът е, че във втория случай ще съществува само един екземпляр на функцията. Което вероятно ще се съгласите, че е по-добър дизайн. И дори да не го направите, просто отнема по-малко памет.

person Imp    schedule 10.08.2012
comment
благодаря много, не разбрах, че всяка дефиниция на метод от конструктора ще бъде различен екземпляр на функцията... - person opensas; 10.08.2012

Няма нищо лошо в този код. Предполага се, че това се има предвид:

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), са специфични за инстанцията.

person KooiInc    schedule 10.08.2012

Както казва 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);
};

Да го обобщим:

  • Добавете свойства, които трябва да се споделят между всички екземпляри към прототипа.
  • Присвояване на специфични за екземпляра данни във функцията на конструктора.
person Felix Kling    schedule 10.08.2012

Точно както arxanas написа в коментара си. Свойствата на данните в прототипа са повече или по-малко подобни на променливите на ниво клас в традиционния oop. И те не се използват ежедневно, освен ако нямате много специфична нужда. Това е всичко.

person Tomasz Zieliński    schedule 10.08.2012

Декларирането на свойства на прототип изобщо не е анти шаблон. Когато гледам 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 обект за двата метода и данни.

Бих казал, че невключването на свойствата на данните в прототипа е антимоделът.

person Greg Burghardt    schedule 28.10.2014

Може би свързано: По принцип модифицирането на обект, който не притежавате, се счита за анти шаблон.

Което означава, че ако не сте създали обекта, значи не притежавате този обект. Включително:

  • Естествени обекти (обект, масив и т.н.)
  • DOM обекти
  • Обекти на обектния модел на браузъра (BOM) (като window)
  • Библиотечни обекти

Източник Поддържаем Javascript от Никълъс С. Закас

person Remi    schedule 23.06.2020