Когда использовать прототипное программирование в 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();

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

JDillon522
источник
Я думаю, что ваш первый пример кода не будет работать, так как проект не выходит за рамки. Чтобы использовать наследование в JS, вы используете прототипирование. Первый пример не предназначен для расширения проекта, поэтому возможность повторного использования может быть ограничена.
Aitch
Да, я случайно поместил projectобъявление внутри функции. Обновлено.
JDillon522
тоже самое. Первый пример статический, второй объектно-ориентированный.
Aitch
1
Если вы используете только ОДИН экземпляр объекта, то нет никакой причины использовать определение прототипа. И то, что вам, похоже, нужно - это формат модуля - для этого есть много вариантов, см .: addyosmani.com/writing-modular-js
c69
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!".

Что касается плюсов и минусов каждого метода, вы можете рассматривать прототипирование как «более эффективный» способ выполнения задач, поскольку он сохраняет ссылку на каждый экземпляр, а не копирует все свойство в каждом объекте. С другой стороны, это пример Tightly Coupling, который является большим нет-нет, пока вы действительно не сможете обосновать причину. 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имеет больше смысла иметь приоритет, потому что все дело в том, что новый экземпляр создается в этот момент.

53777A
источник
Это частично потрясающий ответ! Можете ли вы отредактировать его и углубиться в сравнение, сопоставление двух методов? Первые два абзаца меня смущают тем, какой метод проектирования вы имеете в виду. Ваше упражнение отлично! Я никогда не задумывался о силе прототипирования. Не могли бы вы также привести полу-подробный пример использования случая, когда использовать каждый метод? Еще раз спасибо, это здорово.
JDillon522
@ JDillon522 Рад это слышать. Я постараюсь обновить его в ближайшие часы!
53777A
@ JDillon522 Только что сделал быстрое обновление. Надеюсь, на этот раз все будет ясно.
53777A 17.02.15
@ JDillon522 Еще немного о ваших образцах фрагментов ...
53777A
Хороший пример синглтона. Так что я использовал thisв глупом примере прототипа, потому что thisотносится к его собственным свойствам, включая его методы. Я не изучаю лучшее из чтения о коде, но больше из рассмотрения кода. ( Немного MDN , Object Playground (потрясающе) и некоторые другие). Можете ли вы указать на что-то, что объясняет то, что вы имеете в виду, когда « thisимеет приоритет над prototype»? Я хотел бы изучить это больше.
JDillon522