Відповіді:
Основна відмінність полягає в тому, що функція конструктора використовується з newключовим словом (що призводить до того, що JavaScript автоматично створює новий об'єкт, встановлює thisв межах функції цей об’єкт і повертає об'єкт):
var objFromConstructor = new ConstructorFunction();
Фабрична функція називається як "регулярна" функція:
var objFromFactory = factoryFunction();
Але для того, щоб його вважати "фабрикою", потрібно було б повернути новий екземпляр якогось об'єкта: ви б не назвали це "фабричною" функцією, якби він просто повернув булева або щось подібне. Це не відбувається автоматично, як у випадку new, але це дає більшу гнучкість у деяких випадках.
У дійсно простому прикладі функції, на які посилалося вище, можуть виглядати приблизно так:
function ConstructorFunction() {
this.someProp1 = "1";
this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };
function factoryFunction() {
var obj = {
someProp1 : "1",
someProp2 : "2",
someMethod: function() { /* whatever */ }
};
// other code to manipulate obj in some way here
return obj;
}
Звичайно, ви можете зробити фабричні функції набагато складнішими, ніж простий приклад.
Однією з переваг заводських функцій є те, коли об'єкт, що повертається, може бути декількох різних типів залежно від певного параметра.
someMethodоб’єкти, повернені фабрикою, і тут він стає трохи туманним. Всередині фабричної функції, якщо це просто зробити var obj = { ... , someMethod: function() {}, ... }, це призвело б до того, що кожен повернутий об'єкт тримає іншу копію, someMethodяку ми можемо не хотіти. Саме тут допоможе використання newі prototypeвсередині заводської функції.
newфункцією конструктора; Я подумав, що тут, можливо, потрібно побачити, як замінити конструктори на приклад заводських функцій, і саме там я вважав необхідною послідовність у прикладах. У будь-якому випадку, відповідь досить інформативна. Це був лише пункт, який я хотів підняти, не те, що я якось тягну за якістю відповіді.
newвнутрішньо або використовувати Object.create()для створення об'єкта з певним прототипом.
Більшість книг вчать використовувати конструктори та new
this відноситься до нового об’єкта
Деяким людям подобається спосіб var myFoo = new Foo();читання.
Деталі інстанції просочуються в API виклику (через newвимогу), тому всі абоненти тісно пов'язані з реалізацією конструктора. Якщо вам коли-небудь потрібна додаткова гнучкість на заводі, вам доведеться переробляти всіх абонентів (правда, винятковий випадок, а не правило).
Забуття newє такою поширеною помилкою, слід наполегливо розглянути можливість додавання перевірки на панелі котла, щоб переконатися, що конструктор викликається правильно ( if (!(this instanceof Foo)) { return new Foo() }). EDIT: З ES6 (ES2015) , ви не можете забути newз classконструктором, або конструктор видаватиме помилку.
Якщо ви зробите instanceofперевірку, це залишає неоднозначність щодо того, потрібно чи ні new. На мою думку, цього не повинно бути. Ви фактично коротко замикаєтесь на newвимозі, а це означає, що ви можете усунути недолік №1. Але тоді ви просто отримали фабричну функцію у всіх, крім назви , з додатковою табличкою, великими літерами та менш гнучким thisконтекстом.
Але моя головна стурбованість полягає в тому, що він порушує принцип відкритого / закритого типу. Ви починаєте експортувати конструктор, користувачі починають користуватися конструктором, а потім по дорозі ви розумієте, що вам потрібна гнучкість заводу (наприклад, для переключення реалізації на об'єктивні пули, або на інстанціювання в контекстах виконання, або в володіють більшою гнучкістю успадкування за допомогою прототипічного OO).
Ти ж застрягла. Ви не можете внести зміни, не порушивши весь код, за допомогою якого викликує ваш конструктор new. Наприклад, ви не можете перейти на використання об'єктних пулів для підвищення продуктивності.
Крім того, використання конструкторів дає вам обман instanceof, який не працює в контекстах виконання, і не працює, якщо ваш прототип конструктора буде замінений. Він також провалиться, якщо ви почнете повертатисяthis зі свого конструктора, а потім перейдете до експорту довільного об'єкта, що вам доведеться зробити, щоб активувати заводську поведінку у вашому конструкторі.
Менший код - не потрібно котельня.
Ви можете повернути будь-який довільний об'єкт і використовувати будь-який довільний прототип, що дає вам більше гнучкості для створення різних типів об'єктів, які реалізують один і той же API. Наприклад, медіаплеєр, який може створювати екземпляри HTML5 та флеш-плеєрів, або бібліотеку подій, яка може випромінювати події DOM або події веб-сокета. Фабрики також можуть створювати об'єкти в контекстах виконання, скористатися об'єктами об'єднань та дозволити більш гнучкі моделі прототипічного успадкування.
У вас ніколи не буде необхідності перетворюватися з фабрики на конструктор, тому рефакторинг ніколи не буде проблемою.
Немає двозначності щодо використання new. Не варто. (Це змусить thisсебе поводитись погано, див. Наступний пункт).
thisповодиться так, як зазвичай - тому ви можете використовувати його для доступу до батьківського об'єкта (наприклад, всередині player.create(), thisпосилається на player, як і будь-який інший виклик методу.), callа applyтакож переназначити this, як очікувалося. Якщо ви зберігаєте прототипи на батьківському об'єкті, може бути чудовим способом динамічно заміняти функціональність та ввімкнути дуже гнучкий поліморфізм для обґрунтування об'єкта.
Немає двозначності щодо капіталізації чи ні. Не варто. Інструменти для ворсу будуть скаржитися, і тоді ви будете спокушатися спробувати використовувати new, і тоді ви скасуєте вищеописану вигоду.
Деякі люди , як шлях var myFoo = foo();або var myFoo = foo.create();читають.
newне веде себе так, як очікувалося (див. вище). Рішення: не використовуйте його.
thisне посилається на новий об'єкт (натомість, якщо конструктор викликає позначення крапок або позначення квадратних дужок, наприклад, foo.bar () - thisпосилається на foo- як і будь-який інший метод JavaScript - див. переваги).
newпорушує принцип відкритого / закритого типу. Дивіться medium.com/javascript-scene/… для набагато більшого обговорення, ніж дозволяють ці коментарі.
newключового слова, я не вірю, що newключове слово насправді не забезпечує додаткової читабельності. IMO, здається, нерозумно стрибати через обручі, щоб дозволити абонентам набирати більше.
Конструктор повертає екземпляр класу, до якого ви викликаєте. Фабрична функція може повернути що завгодно. Ви б використовували заводську функцію, коли вам потрібно повернути довільні значення або коли у класу є великий процес настройки.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
newстворює об'єкт, прототипований на User.prototypeі викликає Userстворений об'єкт як його thisзначення.
new трактує вираз аргументу для його операнду як необов'язковий:
let user = new User;
може викликати newдзвінки Userбез аргументів.
newповертає створений ним об'єкт, якщо конструктор не повертає об'єктне значення , яке повертається замість цього. Це крайній випадок, який здебільшого можна ігнорувати.
Об'єкти, створені конструкторськими функціями, успадковують властивості від властивості конструктора prototypeі повертають істину, використовуючи instanceOfоператор функції конструктора.
Вищеописані способи поведінки можуть бути невдалими, якщо ви динамічно змінюєте значення властивості конструктора prototypeпісля того, як ви вже використали конструктор. Це рідко , і його неможливо змінити, якщо конструктор був створений за допомогою classключового слова.
Функції конструктора можна розширити за допомогою extendsключового слова.
Функції конструктора не можуть повернутися nullяк значення помилки. Оскільки це не тип об'єктних даних, він ігнорується new.
function User(name, age) {
return {
name,
age,
}
};
let user = User("Tom", 23);
Тут заводська функція викликається без new. Функція повністю відповідає за пряме або опосередковане використання, якщо її аргументи та тип об'єкта, який вона повертає. У цьому прикладі він повертає простий [Object object] з деякими властивостями, встановленими з аргументів.
Легко приховує складності реалізації об'єкта створення від абонента. Це особливо корисно для функцій нативного коду в браузері.
Фабрична функція не завжди повинна повертати об'єкти одного типу і навіть може повертатися nullяк індикатор помилок.
У простих випадках фабричні функції можуть бути простими за структурою та значенням.
Об'єкти, що повертаються, як правило, не успадковують prototypeвластивості заводської функції , а повертаються falseз instanceOf factoryFunction.
Фабричну функцію не можна безпечно розширити за допомогою extendsключового слова, оскільки розширені об'єкти успадкують від prototypeвластивості заводських функцій замість prototypeвластивості конструктора, використовуваного заводською функцією.
Заводи "завжди" кращі. Тоді при використанні об'єктно-орієнтованих мов
Реалізації (фактичні об'єкти, створені за допомогою нового) не піддаються фабричному користувачеві / споживачеві. Це означає, що розробник фабрики може розширювати та створювати нові реалізації, поки він / вона не порушить договір ... і це дозволяє заводському споживачеві просто отримати вигоду від нового API, не змінюючи їх код ... якщо вони використовували нову, а "нова" реалізація приходить разом, вони повинні перейти і змінити кожен рядок, який використовує "новий", щоб використовувати "нову" реалізацію ... з фабрикою їх код не змінюється ...
Фабрики - кращі за все інше - весняні рамки повністю побудовані навколо цієї ідеї.
Фабрики - це шар абстракції, і, як і всі абстракції, вони мають складну вартість. Зустрічаючи API на заводі, з'ясування того, що є фабрикою для даного API, може бути складним для споживача API. Відкриття конструкцій тривіально.
Вирішуючи питання між автотранспортом і фабриками, ви повинні вирішити, чи складність виправдана вигодою.
Варто зауважити, що конструктори Javascript можуть бути довільними фабриками, повертаючи щось інше, ніж це або невизначене. Таким чином, у js ви можете отримати найкраще з обох світів - відкритий API і об'єднання об'єктів / кешування.
new , Змінюючи поведінку this, Змінюючи повернене значення, Підключення прототипу ref, Увімкнення instanceof(що лежить і не повинно використовуватися для цієї мети). Нібито, все це - "риси". На практиці вони шкодять якості вашого коду.
Для відмінностей Ерік Еліотт дуже добре уточнив,
Але для другого питання:
Коли використовувати один замість іншого?
Якщо ви надходите з об'єктно-орієнтованого фону, функція Constructor виглядає для вас більш природно. таким чином, ви не повинні забути використовувати newключове слово.