Використання "прототипу" проти "цього" в JavaScript?


776

Яка різниця між

var A = function () {
    this.x = function () {
        //do something
    };
};

і

var A = function () { };
A.prototype.x = function () {
    //do something
};


Поняття цього ключового слова пояснено тут прямо тут: scotch.io/@alZami/understanding-this-in-javascript
AL-zami

1
Читання теми «ця» показує, наскільки жахливим є JS і наскільки його принципи незрозумілі для багатьох розробників. Що саме не так з легшими для розуміння мовами? Я думаю, що час розробників підвищувати свій голос, щоб відкинути заплутані технології, які не мають жодної або малої цінності ні для бізнесу, ні для розвитку.
NoChance

На об’єкті a1.x !== a2.x:; про прототип:a1.x === a2.x
Хуан Мендес

Відповіді:


467

Приклади мають дуже різні результати.

Перш ніж переглянути розбіжності, слід зазначити наступне:

  • Прототип конструктора забезпечує спосіб обміну методами та значеннями між примірниками через приватну [[Prototype]]власність екземпляра .
  • Функція цього встановлюється тим, як функція викликається або за допомогою прив'язки (тут не обговорюється). Якщо функція викликається на об'єкті (наприклад myObj.method()), то ця функція в методі посилається на об'єкт. Якщо це не встановлено викликом або використанням bind , воно за замовчуванням застосовується до глобального об'єкта (вікно в браузері) або в строгому режимі, залишається невизначеним.
  • JavaScript - це об'єктно-орієнтована мова, тобто більшість значень - це об'єкти, включаючи функції. (Рядки, числа та булеви не є об'єктами.)

Отже, ось фрагменти, про які йдеться,:

var A = function () {
    this.x = function () {
        //do something
    };
};

У цьому випадку змінній Aприсвоюється значення, яке є посиланням на функцію. Коли ця функція викликається за A()допомогою функції, ця функція не встановлюється викликом, тому вона за замовчуванням відповідає глобальному об'єкту, і вираз this.xє ефективним window.x. Результатом є те, що посилання на вираження функції праворуч призначено window.x.

У випадку:

var A = function () { };
A.prototype.x = function () {
    //do something
};

відбувається щось зовсім інше. У першому рядку змінній Aприсвоюється посилання на функцію. У JavaScript всі об'єкти функцій за замовчуванням мають властивість прототипу , тому немає окремого коду для створення об’єкта A.prototype .

У другому рядку A.prototype.x присвоюється посилання на функцію. Це створить властивість x, якщо воно не існує, або призначить нове значення, якщо воно є. Отже, різниця з першим прикладом, в якому властивість x об'єкта бере участь у виразі.

Ще один приклад наведено нижче. Він схожий на перший (і, можливо, те, про що ви хотіли запитати):

var A = new function () {
    this.x = function () {
        //do something
    };
};

У цьому прикладі newоператор був доданий перед виразом функції, так що функція викликається конструктором. При виклику за newдопомогою функції цієї функції встановлюється посилання на новий об'єкт, приватна [[Prototype]]власність якого встановлена ​​для посилання на відкритий прототип конструктора . Тож у заяві про призначення xбуде створено властивість для цього нового об’єкта. Коли викликається конструктором, функція повертає цей об'єкт за замовчуванням, тому немає необхідності в окремому return this;операторі.

Для того, щоб перевірити , що має й властивість:

console.log(A.x) // function () {
                 //   //do something
                 // };

Це нечасте використання нового, оскільки єдиний спосіб посилання на конструктор - це через A.constructor . Це було б набагато частіше робити:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Інший спосіб досягнення подібного результату - це використання виразу функції, що викликається негайно:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

У цьому випадку Aпризначається повернене значення виклику функції з правого боку. Знову ж таки, оскільки це не встановлено у виклику, воно посилатиметься на глобальний об’єкт та this.xє ефективним window.x. Оскільки функція нічого не повертає, Aматиме значення undefined.

Ці відмінності між двома підходами також виявляються, якщо ви серіалізуєте та десертуєте свої об'єкти Javascript до / з JSON. Методи, визначені в прототипі об'єкта, не серіалізуються під час серіалізації об'єкта, що може бути зручно, коли, наприклад, ви хочете серіалізувати лише частини даних об'єкта, але це не методи:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Пов’язані запитання :

Сторінка: Можливо, між двома підходами не буде суттєвої економії пам’яті, однак використання прототипу для обміну методами та властивостями, швидше за все, буде використовувати менше пам'яті, ніж кожен екземпляр, який має власну копію.

JavaScript не є мовою низького рівня. Можливо, не дуже корисно думати про прототипування чи інші шаблони успадкування як спосіб явно змінити спосіб розподілу пам'яті.


49
@keparo: Ви помиляєтесь. Кожен об’єкт має [внутрішній] об’єкт-прототип (який може бути null), але це дуже відрізняється від prototypeвластивості - яка функціонує та для якої встановлюється прототип усіх екземплярів, коли вони побудовані new. Не можу повірити , що це на самому справі отримав 87 upvotes :-(
Берги

8
"The language is functional"Ви впевнені, що це те, що функціонально означає?
phant0m

23
Я другий, що @Bergi сказав про прототипи. Функції мають властивість прототипу. Усі об'єкти, включаючи функції, мають ще одне внутрішнє властивість, яке можна отримати в деяких браузерах за допомогою Object.getPrototypeOf (myObject) або з протоколом myObject .__. Прото властивість вказує батька об'єкта в ланцюжку прототипів (або об'єкт , з якого цей об'єкт успадковує). Властивість прототипу (яка є лише у функціях) вказувала на об'єкт, який стане батьківським будь-якими об'єктами, які використовують функцію для створення нових об'єктів за допомогою нового ключового слова.
Джим Купер

11
Ця стаття є доволі помилковою і заплутує, як це встановлено. Робота над переписуванням.
RobG

37
Ця відповідь є досить химерною і, здається, повністю пропускає суть питання. Питання, здається, є дуже поширеним питанням визначення властивостей типу в конструкторі та прототипу, але половина відповіді - про те, що буде, якщо ви використовуєте Aфункцію, а інша половина - про незрозумілі та неортодоксальні способи зробити щось прямолінійне.
JLRishe

235

Як говорили інші, у першій версії використання "цього" призводить до того, що кожен екземпляр класу A має свою незалежну копію методу функції "x". Тоді як використання "прототипу" означатиме, що кожен екземпляр класу A буде використовувати одну і ту ж копію методу "x".

Ось якийсь код, щоб показати цю тонку різницю:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Як уже згадували інші, існують різні причини вибору того чи іншого методу. Мій зразок покликаний чітко продемонструвати різницю.


5
Це те, що я б очікував, що це станеться, але коли я створив новий об'єкт після зміни Ax, як вище, я все одно показую "A", якщо я не використовую A, як синглтон. jsbin.com/omida4/2/edit
jellyfishtree

19
Це тому, що мій приклад помилився. Це було неправильно лише два роки. Зітхнути. Але точка все ще діє. Я оновив приклад тим, який насправді працює. Дякуємо, що вказали на це.
Бенрі

4
Це статичний метод! : D

6
так ... "прототип" означає статичний або класний рівень .. який буде поділятися всіма створеними екземплярами ... в той час як "цей" - це метод екземпляра, у якого кожен екземпляр матиме свою власну копію
Aneer Dev

7
Це не статично. Статичний, як використовується в більшості мов OO, означає, що немає залежності від thisоб'єкта, який є власником методу. тобто метод не має об'єкта, який є його власником. У цьому випадку є thisоб'єкт, як показано в прикладі класу А.
CJStuart

152

Візьміть ці 2 приклади:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Більшість людей тут (особливо відповіді з найвищим рейтингом) намагалися пояснити, чим вони відрізняються, не пояснюючи ЧОМУ. Я думаю, що це неправильно, і якщо спочатку ви зрозумієте основи, різниця стане очевидною. Спробуємо спочатку пояснити основи ...

а) Функція - це об'єкт у JavaScript. КОЖЕН об’єкт у JavaScript отримує внутрішню властивість (тобто ви не можете отримати доступ до нього, як і інші властивості, за винятком, можливо, у браузерах, як Chrome), які часто називають __proto__(ви можете фактично вводити anyObject.__proto__Chrome, щоб побачити, на що він посилається. Це просто те , властивість, нічого більше. Властивість у JavaScript = змінна всередині об'єкта, нічого більше. Що роблять змінні? Вони вказують на речі.

То на що вказує ця __proto__властивість? Ну зазвичай інший об’єкт (ми пояснимо, чому пізніше). Єдиний спосіб змусити JavaScript для __proto__властивості НЕ вказувати на інший об’єкт - це використовувати var newObj = Object.create(null). Навіть якщо ви це зробите, __proto__властивість STILL існує як властивість об'єкта, просто воно не вказує на інший об'єкт, на який він вказує null.

Ось де більшість людей плутаються:

Коли ви створюєте нову функцію в JavaScript (що також є об'єктом, пам’ятаєте?), В момент її визначення JavaScript автоматично створює нову властивість цієї функції, яка називається prototype. Спробуй це:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypeВІДПОВІДНО від __proto__готелю. У нашому прикладі "A" тепер має ДВА властивості, які називаються "прототип" та __proto__. Це велика плутанина для людей. prototypeа __proto__властивості жодним чином не пов'язані, вони є окремими речами, що вказують на окремі значення.

Вам може бути цікаво: Чому JavaScript має __proto__властивість, створену для кожного об'єкта? Ну, одне слово: делегація . Коли ви викликаєте властивість на об'єкті, а об'єкт його не має, тоді JavaScript шукає об'єкт, на який посилається, __proto__щоб побачити, чи може він його мати. Якщо його немає, то він переглядає __proto__властивість цього об'єкта і так далі ... поки ланцюг не закінчиться. Таким чином називається прототип ланцюга . Звичайно, якщо __proto__не вказує на об’єкт, а замість цього вказує на велику nullудачу, JavaScript це усвідомлює і поверне вам undefinedвласність.

Вам також може бути цікаво, чому JavaScript створює властивість, викликану prototypeфункцією, коли ви визначаєте функцію? Тому що він намагається вас обдурити, так обдурити, що це працює як мови на основі класу.

Давайте продовжимо наш приклад і створимо "об'єкт" з A:

var a1 = new A();

Щось відбувається на задньому плані, коли ця річ сталася. a1це звичайна змінна, якій було призначено новий, порожній об'єкт.

Те, що ви використовували оператор newперед викликом функції, A()зробило щось ДОПОМОГЛЕ у фоновому режимі. newКлючове слово створюється новий об'єкт , який в даний час заслання a1і цей об'єкт порожній. Ось, що відбувається додатково:

Ми говорили, що в кожному визначенні функції створюється нова властивість, яка називається prototype(до якої ви можете отримати доступ до неї, на відміну від __proto__властивості)? Що ж, ця власність використовується зараз.

Отже, ми зараз в точці, де у нас є свіжоспечений порожній a1предмет. Ми говорили, що всі об’єкти в JavaScript мають внутрішню __proto__властивість, яка вказує на щось ( a1також є), будь то нульовий або інший об'єкт. Те , що newоператор робить те , що він встановлює , що __proto__властивість точки до функції в prototypeвласність. Прочитайте ще раз. В основному це:

a1.__proto__ = A.prototype;

Ми говорили, що A.prototypeце не що інше, як порожній об’єкт (якщо тільки ми не змінимо його на щось інше, перш ніж визначати a1). Отже, в основному a1.__proto__вказує на те саме, на що A.prototypeвказує, що є тим порожнім об'єктом. Вони обоє вказують на той самий об'єкт, який був створений, коли трапився цей рядок:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Тепер відбувається інша річ, коли var a1 = new A()оператор обробляється. В основному A()виконується, і якщо A щось подібне:

var A = function() { this.hey = function() { alert('from A') } };

Все те, що function() { }знаходиться всередині , збирається виконати. Коли ви досягнете this.hey..лінії, thisбуде змінено на a1та ви отримаєте це:

a1.hey = function() { alert('from A') }

Я не буду висвітлювати, чому thisзміни, a1але це чудова відповідь, щоб дізнатися більше.

Отже, підсумовуючи, у вас var a1 = new A()на задньому плані три речі:

  1. Створюється та присвоюється абсолютно новий порожній об’єкт a1.a1 = {}
  2. a1.__proto__властивість призначається вказувати на те саме, що і на A.prototypeточки (інший порожній об'єкт {})

  3. Функція A()виконується за thisдопомогою нового, порожнього об'єкта, створеного на кроці 1 (прочитайте відповідь, на яку я посилався вище, чому thisзмінюється a1)

Тепер спробуємо створити інший об’єкт:

var a2 = new A();

Кроки 1,2,3 повторяться. Ви щось помічаєте? Ключове слово - повтор. Крок 1: a2буде новий порожній об'єкт, крок 2: його __proto__властивість буде вказувати на те саме, що A.prototypeвказує на, а головне, крок 3: функція A()ПРОСТО виконується, що означає, що a2отримає heyвластивість, що містить функцію. a1і a2мають два названих роздільних властивостей, heyякі вказують на 2 СЕПАРАТНІ функції! Тепер ми маємо дублюючі функції у двох і тих же різних об'єктах, що роблять те саме, ой ... Ви можете уявити наслідки для пам'яті цього, якщо у нас 1000 об'єктів, створених за new A, після того, як всі декларації функцій займають більше пам'яті, ніж щось на зразок числа 2. Отже як нам це запобігти?

Пам'ятайте, чому __proto__властивість існує на кожному об'єкті? Отже, якщо ви отримаєте yoManвластивість на a1(яке не існує), його __proto__властивість буде проконсультуватися, що, якщо це об’єкт (і в більшості випадків це є), він перевірить, чи містить він yoMan, а якщо його немає, він звернеться до цього об'єкта __proto__тощо. Якщо це зробити, він візьме це значення властивості і відобразить його вам.

Тож хтось вирішив використати цей факт + той факт, що коли ви створюєте a1, його __proto__властивість вказує на той самий (порожній) об’єкт, який A.prototypeвказує і робить це:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Класно! Тепер, коли ви створюєте a1, він знову проходить усі 3 вищевказані кроки, а на кроці 3 він нічого не робить, оскільки function A()нічого не може виконати. А якщо ми:

a1.hey

Він побачить, що a1він не містить, heyі перевірить його __proto__об’єкт властивості, щоб побачити, чи є у нього, що є випадком.

За допомогою цього підходу ми усуваємо частину з кроку 3, де функції дублюються при кожному створенні нового об'єкта. Замість того , щоб a1і a2наявність окремого heyмайна, в даний час ніхто з них не має його. Який, мабуть, ви вже зрозуміли самі. Це приємна річ ... якщо ви розумієте, __proto__і Function.prototypeтакі питання будуть досить очевидними.

ПРИМІТКА. Деякі люди, як правило, не називають внутрішню властивість прототипу, тому __proto__я використовував це ім'я через пост, щоб чітко відрізнити Functional.prototypeвластивість як дві різні речі.


1
Дійсно ґрунтовна та інформативна відповідь. Я зробив кілька тестів пам’яті, використовуючи структуру об’єктів вище (A.prototype.hey vs object this.hey) і створив 1000 екземплярів кожного. Слід пам’яті для підходу до властивості об’єкта був приблизно на 100 кбіт більшим порівняно з прототипом. Потім я додав ще одну функцію з тією ж метою, яку називали "дурною", і вона лінійно збільшилася до 200 кбіт. Не суттєво, але і не арахіс.
jookyone

Що цікавіше, що метод прототипу був незначно повільнішим, ніж метод, що працює на об'єкті, що працює локально. В цілому, я не впевнений, що JavaScript повинен використовуватися для маніпулювання даними об'єктів, що налічують вище 10 к, тому нехтуючи будь-якою причиною змінювати підходи на основі потенційних ефектів пам'яті. У цей момент робота повинна бути завантажена на сервер.
жартівля

Справа в тому, __proto__і .prototypeце абсолютно різні речі.
Wayou

1
Я не відчуваю задоволення, щоб просто дати вам нагороду ... Молодці!
Крістіанмітк

58

У більшості випадків вони по суті однакові, але друга версія економить пам'ять, оскільки існує лише один екземпляр функції замість окремої функції для кожного об'єкта.

Підставою для використання першої форми є доступ до "приватних членів". Наприклад:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Через правила розміщення Javascript, private_var доступний функції, призначеній для цього.x, але не за межами об'єкта.


1
Дивіться цю публікацію: stackoverflow.com/a/1441692/654708 для прикладу про те, як отримати доступ до приватних членів через прототипи.
GFoley83

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

27

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


Обидва нададуть функцію xдоступною для всіх об'єктів, прототипу яких призначений новий екземпляр A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Спенсер Вільямс

21

Кінцева проблема використання thisзамість цього prototypeполягає в тому, що при переосмисленні методу конструктор базового класу все ще буде посилатися на метод, що переосмислюється. Врахуйте це:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

проти:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

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

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

проти:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1

20

Кожен об'єкт пов'язаний з об'єктом-прототипом. При спробі отримати доступ до властивості, яка не існує, JavaScript перегляне об’єкт-прототип об'єкта для цього властивості та поверне його, якщо він існує.

prototypeВластивість функції конструктора відноситься до об'єкту прототипу всіх примірників , створених з цією функцією при використанні new.


У першому прикладі ви додаєте властивість xдо кожного екземпляра, створеного за допомогою Aфункції.

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

У другому прикладі ви додаєте властивість до об'єкта-прототипу, на який Aвказують усі створені екземпляри .

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

На закінчення, у першому прикладі копія функції призначається кожному екземпляру . У другому прикладі одна копія функції поділяється всіма екземплярами .


1
Проголосував це за те, що це найпростіша відповідь на питання.
Нік Пінеда

1
Мені сподобався ваш прямий підхід !! стукає!
Принц Віджай Пратап

16

Яка різниця? => Багато

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

Розглянемо наступний приклад:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Тепер prototypeструктуру можна застосувати наступним чином:

Різні дорослі мають різний вік, але всі дорослі отримують однакові права.
Отже, ми додаємо його за допомогою прототипу, а не цього.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Давайте розглянемо реалізацію зараз.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Сподіваюсь, це допомагає.


3
+1 Набагато менш заплутаний і більш графічний відповідь, ніж інші. Але вам слід детальніше розробитись, перш ніж надати ці (хороші) приклади.
yerforkferchips

1
Я не впевнений, що "ця версія використовується для включення інкапсуляції, тобто приховування даних". Якщо властивість усередині функції визначено за допомогою "this", як у "this.myProperty = ...", така властивість не є "приватною", і до неї можна отримати доступ з об'єктів поза класом, використовуючи "new".
NoChance

14

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


14

Я знаю, що на це відповіли на смерть, але я хотів би показати фактичний приклад різниці швидкостей.

Функція безпосередньо на об'єкті

Функція на прототипі

Тут ми створюємо 2 000 000 нових об’єктів printметодом у Chrome. Ми зберігаємо кожен об’єкт у масиві. Одяг printна прототип займає приблизно 1/2, ніж довгий.


13

Дозвольте дати вам більш вичерпну відповідь, яку я дізнався під час навчального курсу JavaScript.

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

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

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

Шаблон декоратора об'єкта

Не впевнений, чи ця модель досі актуальна, але вона існує. І про це добре знати. Ви просто передаєте об'єкт і властивість функції декоратора. Декоратор повертає об’єкт властивістю та методом.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Функціональні класи

Функція в JavaScript - це спеціалізований об'єкт. Окрім виклику, функція може зберігати властивості, як і будь-який інший об’єкт.

В цьому випадку Carє функцією ( також думає , об'єкт ) , який може бути викликаний , як ви звикли робити. Він має властивість methods(яка є об'єктом з moveфункцією). Коли Carвикликається extendфункція, викликається функція, яка робить деяку магію, і розширює Carфункцію (об'єкт мислить) методами, визначеними всередині methods.

Цей приклад, хоча і інший, наближається до першого прикладу у питанні.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Прототипові класи

Перші два шаблони дозволяють обговорити використання методів визначення загальних методів або використання методів, визначених вбудовано в тілі конструктора. В обох випадках кожен екземпляр має свою moveфункцію.

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

Однак є один момент, який цікаво знати: кожен prototypeоб'єкт має властивість зручності constructor, яка вказує на функцію (мислити об’єкт), до якої він приєднався.

Щодо останніх трьох рядків:

У цьому прикладі Carпосилання на prototypeоб'єкт, який пов'язує через constructorдо Carсебе, тобто Car.prototype.constructorце Carсаме по собі. Це дозволяє зрозуміти, яка конструкторська функція побудувала певний об’єкт.

amy.constructorпошук не вдається, і, таким чином, делегується до Car.prototype, що має властивість конструктора. Так і amy.constructorє Car.

Крім того, amyє instanceof Car. instanceofОператор працює, бачачи , якщо об'єкт - прототип правого операнда ( Car) можна знайти в будь-якому місці в прототипі лівого операнда ( amy) ланцюга.

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Деякі розробники на початку можна плутати. Дивіться приклад нижче:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

У instanceofоператор повертається false, тому що Dog«прототип сек не може бути знайдений де - небудь в fido" S ланцюга прототипів. fidoце простий об'єкт, який створюється з об'єктом буквально, тобто він просто делегується Object.prototype.

Псевдокласичні зразки

Це дійсно просто ще одна форма прототипного шаблону у спрощеному вигляді та більш звична для тих, хто програмує на Java, наприклад, оскільки він використовує newконструктор.

Це робиться так само, як і в прототипному малюнку, це просто синтаксичний цукор поверх прототипного шаблону.

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

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Нарешті, не повинно бути надто складно зрозуміти, як можна виконати об’єктно-орієнтоване програмування. Є два розділи.

Один розділ, який визначає загальні властивості / методи в прототипі (ланцюзі).

І ще один розділ, де ви ставите визначення, які відрізняють об'єкти один від одного ( locзмінна в прикладах).

Саме це дозволяє нам застосовувати такі поняття, як суперклас або підклас у JavaScript.

Не соромтеся додавати чи редагувати. Ще раз повніше я міг би зробити це вікі спільноти.


Це не дуже чіткий пост, але я вважав, що OO і прототипічне успадкування - це по суті різні школи думок.
Нік Пінеда

Вони є, але можна "робити ОО" різними техніками / думками, чи не так?
Елі

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

Я маю на увазі, якщо ви хочете практикувати стиль OO, а мова пропонує набір методів, які допомагають це зробити, це не обов'язково неправильно.
Елі

11

Я вважаю, що @Matthew Crumley має рацію. Вони функціонально , якщо не структурно, рівнозначні. Якщо ви використовуєте Firebug для перегляду об’єктів, створених за допомогою new, ви можете бачити, що вони однакові. Однак моїм уподобанням було б таке. Я здогадуюсь, що це просто більше схоже на те, до чого я звик у C # / Java. Тобто визначте клас, визначте поля, конструктор та методи.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDIT Не означав, що область змінної була приватною, я просто намагався проілюструвати, як я визначаю свої класи в JavaScript. Ім'я змінної було змінено, щоб відобразити це.


2
_instance_var, як у властивості initializeі x methods do not refer to the _instance_var` в Aекземплярі, але до глобальної. Використовуйте, this._instance_varякщо ви мали намір використовувати _instance_varвластивість Aекземпляра.
Лекенштейн

2
Найцікавіше, що Бенрі також допустив таку помилку, яку було виявлено також через два роки: p
Lekensteyn

10

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

Я зібрав jsperf, щоб показати це. Існує різка різниця у часі, необхідному для інстанціювання класу, хоча це дійсно актуально лише у тому випадку, коли ви робите багато екземплярів.

http://jsperf.com/functions-in-constructor-vs-prototype


8

Подумайте про статично набрану мову, речі на них prototypeє статичними, а речі - на thisекземпляри.

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