Javascript, когда использовать прототипы

Я хотел бы понять, когда целесообразно использовать прототипы в js. Должны ли они всегда использоваться? Или случаются случаи, когда использование их не является предпочтительным и/или налагает штраф за исполнение?

При поиске по этому сайту общих методов для пространства имен в js, кажется, что большинство используют не-прототипную реализацию: просто используя объект или объект функции для инкапсуляции пространства имен.

Исходя из языка, основанного на классе, трудно не пытаться провести параллели и думать, что прототипы похожи на "классы", а реализации пространств имен, о которых я упоминал, похожи на статические методы.

Ответ 1

Прототипы - это оптимизация.

Отличным примером их использования является библиотека jQuery. Каждый раз, когда вы получаете объект jQuery с помощью $('.someClass'), этот объект имеет десятки "методов". Библиотека могла бы достичь этого, возвращая объект:

return {
   show: function() { ... },
   hide: function() { ... },
   css: function() { ... },
   animate: function() { ... },
   // etc...
};

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

Вместо этого эти методы определены в прототипе, и все объекты jQuery "наследуют" этот прототип, чтобы получить все эти методы при очень низкой стоимости исполнения.

Одна жизненно важная часть того, как jQuery получает это право, заключается в том, что это скрыто от программиста. Он рассматривал чисто оптимизацию, а не как то, о чем вам нужно беспокоиться при использовании библиотеки.

Проблема с JavaScript заключается в том, что функции голого конструктора требуют, чтобы вызывающий абонент забывал префикс их с помощью new, иначе они обычно не работают. Для этого нет веской причины. jQuery получает это право, скрывая эту бессмыслицу за обычной функцией, $, поэтому вам не нужно заботиться о том, как реализуются объекты.

Чтобы вы могли удобно создать объект с указанным прототипом, ECMAScript 5 включает стандартную функцию Object.create. Очень упрощенная версия будет выглядеть так:

Object.create = function(prototype) {
    var Type = function () {};
    Type.prototype = prototype;
    return new Type();
};

Он просто заботится о боли в написании функции конструктора, а затем вызывает ее с помощью new.

Когда вы избежите прототипов?

Полезное сравнение с популярными языками OO, такими как Java и С#. Они поддерживают два вида наследования:

  • интерфейс наследование, где вы implement a interface, чтобы класс предоставлял свою собственную уникальную реализацию для каждого члена интерфейса.
  • реализация наследование, где вы extend a class, который предоставляет стандартные реализации некоторых методов.

В JavaScript прототипическое наследование является наследованием реализации. Таким образом, в тех ситуациях, когда (на С# или Java) вы получали из базового класса, чтобы получить поведение по умолчанию, а затем вы делаете небольшие изменения с помощью переопределений, тогда в JavaScript имеет место прототипное наследование.

Однако, если вы находитесь в ситуации, когда вы использовали интерфейсы на С# или Java, вам не нужна какая-либо особенность языка в JavaScript. Нет необходимости явно объявлять что-то, что представляет интерфейс, и нет необходимости отмечать объекты как "реализующие" этот интерфейс:

var duck = {
    quack: function() { ... }
};

duck.quack(); // we're satisfied it a duck!

Другими словами, если каждый "тип" объекта имеет свои собственные определения "методов", тогда нет никакой наследуемости от прототипа. После этого это зависит от того, сколько экземпляров вы выделяете для каждого типа. Но во многих модульных конструкциях существует только один экземпляр заданного типа.

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

Ответ 2

Вы должны использовать прототипы, если хотите объявить "нестатический" метод объекта.

var myObject = function () {

};

myObject.prototype.getA = function (){
  alert("A");
};

myObject.getB = function (){
  alert("B");
};

myObject.getB();  // This works fine

myObject.getA();  // Error!

var myPrototypeCopy = new myObject();
myPrototypeCopy.getA();  // This works, too.

Ответ 3

Одной из причин использования встроенного объекта prototype является то, что вы будете дублировать объект несколько раз, чтобы использовать общие функции. Прикрепляя методы к прототипу, вы можете сэкономить на методах дублирования, создаваемых за каждый экземпляр new. Но когда вы присоединяете метод к prototype, все экземпляры будут иметь доступ к этим методам.

Скажем, у вас есть базовый класс Car() class/object.

function Car() {
    // do some car stuff
}

то вы создаете несколько экземпляров Car().

var volvo = new Car(),
    saab = new Car();

Теперь вы знаете, что каждый автомобиль нужно будет водить, включать и т.д. Вместо того, чтобы привязывать метод непосредственно к классу Car() (который занимает память на каждый созданный экземпляр), вы можете прикрепить методы к прототипу вместо этого (создание методов только один раз), поэтому предоставление доступа к этим методам как для новых volvo, так и saab.

// just mapping for less typing
Car.fn = Car.prototype;

Car.fn.drive = function () {
    console.log("they see me rollin'");
};
Car.fn.honk = function () {
    console.log("HONK!!!");
}

volvo.honk();
// => HONK!!!
saab.drive();
// => they see me rollin'

Ответ 4

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

Изменение методов объектов-прототипов или методов добавления мгновенно изменяет характер всех экземпляров соответствующего типа (ов).

Теперь именно то, почему вы все это делаете, в основном зависит от вашего собственного дизайна приложения и от того, что вам нужно делать в клиентском коде. (Целая другая история - это код внутри сервера, гораздо проще представить себе более крупный "OO" код там.)

Ответ 5

Если я объясню в терминологии, основанной на классе, тогда Person является классом, метод walk() - прототипом. Таким образом, walk() будет иметь свое существование только после того, как вы создадите новый объект с этим.

Итак, если вы хотите создать копии объекта, такие как Person, вы можете создать много пользователей. Прототип - хорошее решение, поскольку оно сохраняет память путем совместного использования/наследования той же копии функции для каждого из объектов в памяти.

В то время как статический не очень помогает в таком сценарии.

function Person(){
this.name = "anonymous";
}

// its instance method and can access objects data data 
Person.prototype.walk = function(){
alert("person has started walking.");
}
// its like static method
Person.ProcessPerson = function(Person p){
alert("Persons name is = " + p.name);
}

var userOne = new Person();
var userTwo = new Person();

//Call instance methods
userOne.walk();

//Call static methods
Person.ProcessPerson(userTwo);

Итак, с этим более похожим методом экземпляра. Объектный подход похож на статические методы.

https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript