Коли використовувати прототипічне програмування в JavaScript


15

Я витратив багато часу на розробку простих віджетів для проектів наступним чином:

var project = project || {};

(function() {

  project.elements = {
    prop1: val1,
    prop2: val2
  }

  project.method1 = function(val) {
    // Do this
  }

  project.method2 = function(val) {
    // Do that
  }

  project.init = function() {
    project.method1(project.elements.prop1)
    project.method2(project.elements.prop2)
  }
})()

project.init();

Але я почав змінювати свій формат на такий:

function Project() {
  this.elements = {
    prop1: val1,
    prop2: val2
  }

  this.method_one(this.elements.prop1);
  this.method_two(this.elements.prop2);
}

Project.prototype.method_one = function (val) {
  // 
};

Project.prototype.method_two = function (val) {
  //
};

new Project();

Зрозуміло, це нерозумні приклади, тому не обмотуйтеся навколо вісі. Але в чому полягає функціональна різниця і коли я повинен вибрати те чи інше?


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

Так, я випадково помістив projectдекларацію всередині функції. Оновлено.
JDillon522

те ж саме. перший приклад - статичний, другий - об'єктно-орієнтований.
Aitch

1
Якщо ви використовуєте лише ОДИН екземпляр об'єкта, тоді немає жодної причини використовувати визначення прототипу. І те, що вам, здається, потрібно - це формат модуля - є багато варіантів для цього, дивіться: addyosmani.com/writing-modular-js
c69

1
Перший для однотонного шаблону Другий для недінгтонного (коли в 1 класі може бути багато об’єктів)
Kokizzu

Відповіді:


6

Перша різниця може бути зведена як: thisвідноситься до інстанції класу. prototypeстосується Визначення .

Скажімо, у нас є такий клас:

var Flight = function ( number ) { this.number = number; };

Отже, тут ми приєднуємося this.numberдо кожного примірника класу, і це має сенс, оскільки кожен Flightповинен мати свій номер рейсу.

var flightOne = new Flight( "ABC" );
var flightTwo = new Flight( "XYZ" );

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

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

Flight.prototype.getNumber = function () { return this.number; };

Друга відмінність полягає в тому, як JavaScript шукає властивість об'єкта. Коли ви шукаєте Object.whatever, JavaScript проходить весь шлях до основного об'єкта Object (об’єкта, від якого все інше успадкував), і як тільки він знайде відповідність, він повернеться або зателефонує.

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

Подивимося, як це відбувається насправді.

Спершу зауважте, що [майже] все є об’єктами в JavaScript. Спробуйте це:

typeof null

Тепер давайте подивимося, що знаходиться всередині Object(зверніть увагу на верхній регістр Oта. наприкінці). У Інструментах для розробників Google Chrome під час входу .ви отримаєте список доступних властивостей всередині цього конкретного об’єкта.

Object.

Тепер зробіть те саме Function :

Function.

Ви можете помітити name метод. Просто зайдіть і розпаліть його, і подивимося, що станеться:

Object.name
Function.name

Тепер давайте створимо функцію:

var myFunc = function () {};

І давайте подивимось, чи отримали ми name метод і тут:

myFunc.name

У вас повинен вийти порожній рядок, але це нормально. Ви не повинні отримувати помилку або виняток.

Тепер давайте щось додамо до цього богоподібного Objectі подивимось, чи отримаємо ми його і в інших місцях?

Object.prototype.test = "Okay!";

І ось ви йдете:

Object.prototype.test
Function.prototype.test
myFunc.prototype.test

У всіх випадках ви повинні бачити "Okay!" .

Щодо плюсів і мінусів кожного методу, ви можете розглядати прототипування як "більш ефективний" спосіб виконання дій, оскільки він зберігає посилання на кожен примірник, а не копіює всю властивість у кожному об'єкті. З іншого боку, це приклад жорсткого зчеплення який є великим «ні-ні», поки ви дійсно не зможете виправдати причину. thisє набагато складнішим, оскільки стосується контексту. Ви можете знайти багато хороших ресурсів безкоштовно в Інтернеті.

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

Якщо вам потрібно мати властивість, яке відповідатиме кожному примірнику класу, тоді використовуйте this. Якщо вам потрібно мати властивість, щоб функціонувати однаково у кожному екземплярі, тоді використовуйте prototype.

Оновлення

Що стосується ваших фрагментів зразків, то перший - це приклад Singleton , тому його має сенс використовувати thisв тілі об'єкта. Ви також можете покращити свій приклад, зробивши його таким чином модульним (і вам також не потрібно завжди користуватися this).

/* Assuming it will run in a web browser */
(function (window) {
    window.myApp = {
        ...
    }
})( window );

/* And in other pages ... */
(function (myApp) {
    myApp.Module = {
        ...
    }
})( myApp );

/* And if you prefer Encapsulation */
(function (myApp) {
    myApp.Module = {
         "foo": "Foo",
         "bar": function ( string ) {
             return string;
         },
         return {
             "foor": foo,
             "bar": bar
         }
    }
})( myApp );

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

Оновлення

Щоб детальніше зупинитись на thisперевазі, prototypeя можу показати вам приклад і розповісти, як це можна пояснити, але у мене немає жодного зовнішнього ресурсу, який би його створив.

Приклад дуже простий:

var myClass = function () { this.foo = "Foo"; };
myClass.prototype.foo = "nice try!";
myClass.prototype.bar = "Bar";

var obj = new myClass;
obj.foo;     // Still contains "Foo" ...
obj.bar;     // Contains "Bar" as expected

Пояснення, як ми знаємо, thisмає відношення до контексту. Тож воно не буде існувати, поки контекст не буде готовий. Коли контекст готовий? Коли створюється новий екземпляр! Ви повинні вгадати решту зараз! Це означає, що, хоч і є prototypeвизначення, але thisмає більше сенсу брати перевагу, оскільки мова йде про новий екземпляр, який створюється в цей момент.


Це частково епічно приголомшлива відповідь! Чи можете ви відредагувати його та перейти до деталей, порівнюючи та протиставляючи ці два методи? Ваші перші два абзаци плутають мене щодо того, про який метод дизайну ви посилаєтесь. Ваша вправа відмінна! Я ніколи не задумувався над силою такого прототипування. Чи можете ви також навести напівдокладний приклад використання випадків використання кожного методу? Ще раз дякую, це чудово.
JDillon522

@ JDillon522 Радий почути це. Я спробую оновити його в найближчі години!
53777A

@ JDillon522 Просто зробив швидке оновлення. Сподіваюся, цього разу буде зрозуміліше.
53777A

@ JDillon522 Ще трохи про ваші фрагменти зразків ...
53777A

Гарний приклад одиночки. Тому я використовував thisу прикладі нерозумного прототипу, оскільки thisпосилається на власні властивості, включаючи його методи. Я не вчуся найкраще, читаючи про код, але більше, не дивлячись на код. ( MDN біт на ньому , Object Playground (дивовижно) та кілька інших). Чи можете ви вказати на щось, що пояснює, що ви маєте на увазі про " thisмає пріоритет над prototype"? Я хотів би детальніше розглянути його.
JDillon522
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.