Объявление объекта объекта javascript в функции конструктора vs. в прототипе

При создании объектов javascript я могу поместить объявление метода либо в конструкторскую функцию, либо в прототип. Например, скажем, я хочу, чтобы класс Dog имел свойство Name и метод Bark. Я могу поместить объявление метода Барк в конструкторскую функцию:

var Dog = function(name) {
    this.Name = name;
    this.Bark = function() {
        alert(this.Name + " bark");
    };
}

или я мог бы вставить в качестве метода объект-прототип:

var Dog = function(name) {
    this.Name = name;
}

Dog.prototype.Bark = function() {
    alert(this.Name + " bark");
};

Когда я создаю объекты типа Dog, оба подхода работают нормально:

var dog = new Dog("Fido");
dog.Bark();  //Both approaches show "Fido bark"

Предпочитаю ли я один из этих подходов к другому? Существуют ли какие-либо преимущества для использования одного над другим? За кулисами эти два подхода в конечном итоге делают то же самое? Какой подход предпочитает большинство людей?

Спасибо за помощь.

Ответ 1

В приведенном примере вы должны использовать прототипный подход. В общем, это зависит. Основное преимущество первого подхода (инициализация методов в конструкторе) заключается в том, что вы можете использовать преимущества замыканий, используя локальные переменные, определенные в конструкторе в ваших методах. Эти переменные напрямую недоступны вне функции-конструктора, поэтому эффективно "private", что означает, что ваш API чище, чем если бы эти переменные были определены как свойства объекта. Некоторые общие правила:

  • Если ваши методы не используют локальные переменные, определенные в вашем конструкторе (в вашем примере это не так), используйте подход прототипа.
  • Если вы создаете много Dog s, используйте прототипный подход. Таким образом, все "экземпляры" (т.е. Объекты, созданные конструктором Dog) будут совместно использовать один набор функций, тогда как путь конструктора, новый набор функций создается каждый раз, когда вызывается конструктор Dog, используя больше памяти.
  • Если вы создаете небольшое число Dog и обнаружите, что использование локальных переменных "private" в вашем конструкторе улучшает ваш код, это может быть лучшим подходом. Используйте свое мнение и делайте некоторые тесты, если важны проблемы с производительностью или памятью.

Можно использовать гибридный подход, при котором в конструкторе определяются только методы, требующие доступа к локальным переменным конструктора, а другим прототипом назначаются другие методы.

Например, приведенный ниже код использует локальную переменную в конструкторе, чтобы отслеживать, сколько раз эта собака лаяла, сохраняя фактическое число частным, поэтому методы, связанные с лаем, определены внутри конструктора. Поворот хвоста не требует доступа к количеству лаков, поэтому этот метод может быть определен на прототипе.

var Dog = function(name) {
    this.name = name;

    var barkCount = 0;

    this.bark = function() {
        barkCount++;
        alert(this.name + " bark");
    };

    this.getBarkCount = function() {
        alert(this.name + " has barked " + barkCount + " times");
    };
};

Dog.prototype.wagTail = function() {
    alert(this.name + " wagging tail");
};

var dog = new Dog("Dave");
dog.bark();
dog.bark();
dog.getBarkCount();
dog.wagTail();

Ответ 2

Эти два варианта: первый сохранит ссылку на метод только на объект-прототип, тогда как второе решение сохранит метод на каждом из объектов. Это означает, что каждый объект будет содержать дополнительный указатель и, следовательно, будет занимать немного больше памяти.

Метод per-object позволяет методу ссылаться на переменные в конструкторе (замыкание), поэтому он позволяет вам получить доступ к некоторым данным, к которым вы не можете получить доступ из прототипов.

Наконец, метод прототипа может быть изменен позже, то есть вы можете переопределить Bark во время выполнения объекта-прототипа, и это изменение будет работать для всех объектов с этим прототипом (так как метод всегда просматривается через прототип).

Ответ 3

Подавляющее большинство javascript-кода, которое я видел, использует метод прототипа. Я думаю, что есть три причины для этого, что я могу думать о моей голове.

Во-первых, вы избегаете того, чтобы каждый класс был огромным конструктором: логика конструктора входит в конструкторскую функцию, логика для других методов объявлена ​​в другом месте - это в основном явная вещь/разделение проблем, но в javascript вы нужно каждый бит ясности вы можете получить в свои руки.

Вторая - эффективность. Когда вы объявляете методы в конструкторе, вы создаете новый экземпляр объекта функции для каждого экземпляра объекта, а также привязываете область действия конструктора к каждой из этих функций (то есть, они могут ссылаться, например, на аргументы конструктору, который никогда не может быть gc'd до тех пор, пока объект живет). Когда вы объявляете методы на прототипе, есть единственная копия объекта функции, которая используется всеми экземплярами - свойства прототипа не копируются в экземпляры.

Третья причина заключается в том, что вы можете "расширить" класс различными способами, когда используете метод прототипа, например цепочку прототипов, используемую конструкцией класса Backbone.js и CoffeeScript.