Javascript, коли використовувати прототипи


93

Я хотів би зрозуміти, коли доречно використовувати методи прототипу в js. Чи завжди їх слід використовувати? Або бувають випадки, коли їх використання не є кращим та / або вимагає покарання за виконання?

Здійснюючи пошук на цьому веб-сайті за загальними методами простору імен у js, більшість використовують непрототипну реалізацію: просто використовуючи об’єкт або об’єкт функції для інкапсуляції простору імен.

Виходячи з мови, що базується на класах, важко не спробувати провести паралелі і думати, що прототипи схожі на "класи", а реалізації простору імен, які я згадав, подібні на статичні методи.

Відповіді:


133

Прототипи - це оптимізація .

Прекрасним прикладом їх хорошого використання є бібліотека 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 та C #. Вони підтримують два види успадкування:

  • Інтерфейс успадкування, де ви , що клас надає свою власну унікальну реалізацію для кожного члена інтерфейсу.implementinterface
  • Реалізація успадкування, де ви , що забезпечує по замовчуванням реалізації деяких методів.extendclass

У JavaScript прототипне успадкування є різновидом успадкування реалізації . Отже, у тих ситуаціях, коли (у C # або Java) ви отримали б базовий клас, щоб отримати поведінку за замовчуванням, до якої ви потім вносите невеликі зміни за допомогою перевизначень, тоді в JavaScript прототипове успадкування має сенс.

Однак, якщо ви потрапили в ситуацію, коли б ви використовували інтерфейси на C # або Java, тоді вам не потрібна жодна особлива мовна функція в JavaScript. Немає необхідності явно оголошувати щось, що представляє інтерфейс, і не потрібно позначати об'єкти як "реалізуючі" цей інтерфейс:

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

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

Іншими словами, якщо кожен "тип" об'єкта має власні визначення "методів", тоді спадкування від прототипу не має значення. Після цього це залежить від того, скільки екземплярів ви виділите для кожного типу. Але в багатьох модульних конструкціях існує лише один примірник даного типу.

Насправді багато людей припускають, що реалізація успадкування є злом . Тобто, якщо є деякі загальні операції для типу, то, можливо, це зрозуміліше, якщо вони не введені в базовий / супер клас, а натомість просто виставляються як звичайні функції в якомусь модулі, якому ви передаєте об’єкт (и) ви хочете, щоб вони оперували.


1
Гарне пояснення. Тоді ви погодитесь, що, оскільки ви вважаєте прототипи оптимізацією, їх завжди можна використовувати для вдосконалення вашого коду? Мені цікаво, чи є випадки, коли використання прототипів не має сенсу або насправді несе покарання за продуктивність.
opl

У своїх подальших заходах ви зазначаєте, що "це залежить від того, скільки екземплярів ви виділите для кожного типу". Але приклад, на який ви посилаєтесь, не використовує прототипи. Де поняття про виділення екземпляру (ви все одно використовували б тут "новий")? Також: скажімо, метод quack мав параметр - чи буде кожне виклик duck.quack (param) створювати новий об’єкт у пам’яті (можливо, це не має значення, якщо у нього є параметр чи ні)?
opl

3
1. Я мав на увазі, що якщо існує велика кількість екземплярів одного типу качок, то має сенс змінити приклад таким чином, щоб quackфункція була в прототипі, з яким пов’язано безліч екземплярів качок. 2. Синтаксис об’єктного літералу { ... }створює екземпляр (немає необхідності використовувати newз ним). 3. Виклик будь-якої функції JS призводить до створення принаймні одного об'єкта в пам'яті - він називається argumentsоб'єктом і зберігає аргументи, передані у виклику: developer.mozilla.org/en/JavaScript/Reference/…
Даніель Ервікер

Дякую, я прийняв вашу відповідь. Але я все ще маю легку плутанину з вашим пунктом (1): я не розумію, що ви маєте на увазі під "великою кількістю випадків одного виду качок". Як ви сказали в (3), кожного разу, коли ви викликаєте функцію JS, один об'єкт створюється в пам'яті - отже, навіть якщо у вас просто один тип качки, чи не буде вам виділяти пам'ять кожного разу, коли ви викликаєте функцію качки (у у якому випадку завжди було б сенсом використовувати прототип)?
opl

11
+1 Порівняння з jQuery стало першим чітким і стислим поясненням того, коли і навіщо використовувати прототипи, які я прочитав. Велике спасибі.
GFoley83

46

Вам слід використовувати прототипи, якщо ви хочете оголосити "нестатичний" метод об'єкта.

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.

@keatsKelleher, але ми можемо створити нестатичний метод для об'єкта, просто визначивши метод всередині функції конструктора, використовуючи thisприклад, this.getA = function(){alert("A")}чи не так?
Amr Labib

17

Однією з причин використання вбудованого prototypeоб’єкта є те, що ви будете дублювати об’єкт кілька разів, що матиме спільну функціональність. Додавши методи до прототипу, ви можете заощадити на методах дублювання, що створюються для кожного newекземпляра. Але коли ви prototypeприєднаєте метод до , усі екземпляри матимуть доступ до цих методів.

Скажімо, у вас є базовий Car()клас / об’єкт.

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'

2
насправді це неправильно. volvo.honk () не буде працювати, оскільки ви повністю замінили об'єкт-прототип, а не розширили його. Якби ви робили щось подібне, це працювало б так, як ви очікували: Car.prototype.honk = function () {console.log ('HONK');} volvo.honk (); // 'HONK'
29er

1
@ 29er - у тому, як я написав цей приклад, ви маєте рацію. Порядок має значення. Якби я мав зберегти цей приклад як є, Car.prototype = { ... }перед викликом а, new Car()як показано у цьому jsfiddle: jsfiddle.net/mxacA . Що стосується Вашого аргументу, це буде правильний спосіб зробити це: jsfiddle.net/Embnp . Забавно те, що я не пам’ятаю відповіді на це питання =)
hellatan

@hellatan ви можете це виправити, встановивши конструктор: Car to, оскільки ви переписали властивість прототипу літералом об'єкта.
Джош Бедо

@josh дякую, що вказав на це. Я оновив свою відповідь, щоб не перезаписувати прототип літералом об’єкта, як це мало бути з самого початку.
hellatan

12

Помістіть функції на об'єкт-прототип, коли ви збираєтеся створити безліч копій об'єкта певного типу, і всі вони повинні поділяти загальну поведінку. Таким чином ви заощадите трохи пам’яті, маючи лише одну копію кожної функції, але це лише найпростіша перевага.

Зміна методів на об'єктах-прототипах або додавання методів миттєво змінює характер усіх екземплярів відповідних типів.

Тепер саме те , чому ви робите всі ці речі, в основному залежить від вашого власного дизайну програми та від того, що вам потрібно робити в коді на стороні клієнта. (Зовсім інша історія - це код всередині сервера; набагато простіше уявити, щоб робити там більш масштабний код "ОО".)


отже, коли я створюю новий об'єкт за допомогою методів прототипів (за допомогою нового ключового слова), тоді цей об'єкт не отримує нову копію кожної функції (просто свого роду вказівник)? Якщо це так, чому б ви не хотіли використовувати прототип?
opl

типу @marcel, d'oh ... =)
hellatan

@opi так, ти маєш рацію - копії не зроблено. Натомість символи (імена властивостей) на об'єкті-прототипі є якимось природним чином "там" як віртуальні частини кожного об'єкта екземпляра. Єдиною причиною, через яку люди не хочуть турбуватись, є випадки, коли предмети недовговічні та чіткі, або коли не так багато "поведінки", якими можна поділитися.
Пойнті

3

Якщо я пояснюю на основі класу термін, то людина - це клас, 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.