Шаблон JavaScript для нескольких конструкторов

124

Мне нужны разные конструкторы для моих экземпляров. Каков общий шаблон для этого?

codeholic
источник
будьте более конкретны, пожалуйста. вам нужны конструкторы с разными наборами параметров?
gblazex
Можете ли вы иметь более одного конструктора в Javascript?
Дуг Хауф,
Да и нет @DougHauf. Да, потому что ответ, предоставленный bobince, дает возможность продемонстрировать аналогичное поведение. Нет, потому что, если вам нужно несколько различных функций-конструкторов (каждая из которых использует один и тот же объект-прототип), как будет установлено свойство конструктора объекта-прототипа (поскольку свойство конструктора может указывать только на одну функцию-конструктор).
Мойке
3
Все эти ответы старые / неидеальные. Я слишком ленив , чтобы напечатать ответ, но вы можете передать объект вокруг функций и конструкторов , а затем использовать ключи, как вы бы аргументы, например: function ({ oneThing = 7, otherThing = defaultValue } = {}) { }. Дополнение, которое = {}я добавил, - это еще один трюк, которому я недавно научился, на случай, если вы хотите, чтобы пользователь вообще не передавал никаких объектов и использовал все значения по умолчанию.
Эндрю
1
Продолжение: Вот несколько хороших способов решить эту проблему: stackoverflow.com/a/32626901/1599699 stackoverflow.com/a/41051984/1599699 stackoverflow.com/a/48287734/1599699 Мне особенно нравится последний из них. Поддержка нескольких конструкторов с использованием статических фабричных функций в качестве конструкторов ( return new this();, return new this.otherStaticFactoryFunction();и т. д.)!
Эндрю

Ответы:

120

В JavaScript нет перегрузки функций, в том числе для методов или конструкторов.

Если вы хотите, чтобы функция вела себя по-разному в зависимости от количества и типов параметров, которые вы ей передаете, вам придется обнюхивать их вручную. JavaScript с радостью вызовет функцию с большим или меньшим количеством аргументов, чем заявлено.

function foo(a, b) {
    if (b===undefined) // parameter was omitted in call
        b= 'some default value';

    if (typeof(a)==='string')
        this._constructInSomeWay(a, b);
    else if (a instanceof MyType)
        this._constructInSomeOtherWay(a, b);
}

Вы также можете получить доступ argumentsкак к массиву, чтобы получить дополнительные аргументы.

Если вам нужны более сложные аргументы, может быть хорошей идеей поместить некоторые или все из них в поиск по объектам:

function bar(argmap) {
    if ('optionalparam' in argmap)
        this._constructInSomeWay(argmap.param, argmap.optionalparam);
    ...
}

bar({param: 1, optionalparam: 2})

Python демонстрирует, как аргументы по умолчанию и именованные аргументы могут использоваться для охвата большинства случаев использования более практичным и изящным способом, чем перегрузка функций. JavaScript, не так уж и много.

bobince
источник
2
Спасибо, это действительно здорово. Я бы сказал, что второй вариант полезен не только когда у вас есть сложные аргументы, но и простые, но трудно различимые аргументы, например, поддерживающий MyObj({foo: "foo"})плюс MyObj({bar: "bar"}). MyObj имеет два конструктора, но оба принимают один аргумент, который является строкой :-)
Alex Dean
1
Можете ли вы добавить что-то еще в пример кода для этого конкретного примера.
Doug Hauf
Привет, @DougHauf! В книге Крокфорда «JavaScript: хорошие части» есть раздел под названием «Спецификаторы объектов», множество примеров отсылается к нему в Интернете.
Мойке
29

Как вы его нашли?

function Foobar(foobar) {
    this.foobar = foobar;
}

Foobar.prototype = {
    foobar: null
};

Foobar.fromComponents = function(foo, bar) {
    var foobar = foo + bar;
    return new this(foobar);
};
codeholic
источник
2
вернуть новый this (foobar); не работает. Меняю по возвращении новый Foobar (foobar); и все работает правильно.
isxaker
10
Я не понимаю. Можете ли вы добавить код там, где вы его действительно используете? Собираетесь ли вам каждый раз звонить fromComponents? Потому что это не совсем конструктор, а скорее вспомогательная функция. Ответ @bobince кажется более точным.
hofnarwillie
1
@hofnarwillie Хотя это не совсем точный конструктор, при использовании статического метода он работает примерно так же, как например var foobarObj = Foobar.fromComponents (foo, bar); это все, что вам нужно для создания нового объекта с альтернативными аргументами.
Натан Уильямс
21

вы можете использовать класс со статическими методами, которые возвращают экземпляр этого класса

    class MyClass {
        constructor(a,b,c,d){
            this.a = a
            this.b = b
            this.c = c
            this.d = d
        }
        static BAndCInstance(b,c){
            return new MyClass(null,b,c)
        }
        static BAndDInstance(b,d){
            return new MyClass(null,b, null,d)
        }
    }

    //new Instance just with a and other is nul this can
    //use for other params that are first in constructor
    const myclass=new MyClass(a)

    //an Instance that has b and c params
    const instanceWithBAndC = MyClass.BAndCInstance(b,c)

    //another example for b and d
    const instanceWithBAndD = MyClass.BAndDInstance(b,d)

с этим шаблоном вы можете создать мульти-конструктор

Махди Шахбази
источник
3
Это лучший ответ. Остальные прибегают к синтаксическому анализу массивов и проделывают кучу ненужной работы.
Блейн Таунсенд
13

Не хотелось делать это вручную, как в ответе bobince, поэтому я просто полностью сорвал шаблон параметров плагина jQuery.

Вот конструктор:

//default constructor for Preset 'class'
function Preset(params) {
    var properties = $.extend({
        //these are the defaults
        id: null,
        name: null,
        inItems: [],
        outItems: [],
    }, params);

    console.log('Preset instantiated');
    this.id = properties.id;
    this.name = properties.name;
    this.inItems = properties.inItems;
    this.outItems = properties.outItems;
}

Вот разные способы создания экземпляров:

presetNoParams = new Preset(); 
presetEmptyParams = new Preset({});
presetSomeParams = new Preset({id: 666, inItems:['item_1', 'item_2']});
presetAllParams = new Preset({id: 666, name: 'SOpreset', inItems: ['item_1', 'item_2'], outItems: ['item_3', 'item_4']});

И вот что из этого получилось:

presetNoParams
Preset {id: null, name: null, inItems: Array[0], outItems: Array[0]}

presetEmptyParams
Preset {id: null, name: null, inItems: Array[0], outItems: Array[0]}

presetSomeParams
Preset {id: 666, name: null, inItems: Array[2], outItems: Array[0]}

presetAllParams
Preset {id: 666, name: "SOpreset", inItems: Array[2], outItems: Array[2]}
Джейкоб Маккей
источник
Я использовал тот же шаблон в node.js сейчас: npmjs.com/package/extend
Джейкоб Маккей
10

Продолжая с ответом eruciform, вы можете связать свой newвызов со своим initметодом.

function Foo () {
    this.bar = 'baz';
}

Foo.prototype.init_1 = function (bar) {
    this.bar = bar;
    return this;
};

Foo.prototype.init_2 = function (baz) {
    this.bar = 'something to do with '+baz;
    return this;
};

var a = new Foo().init_1('constructor 1');
var b = new Foo().init_2('constructor 2');
laughingbovine
источник
Итак, в основном вы здесь берете объект Foo и затем вызываете параметры init_1 и init_2 с функциями прототипа. Если в ваших init_1 и init_2 есть слово function.
Doug Hauf
должна ли быть точка с запятой после} в первом Foo ().
Doug Hauf
Спасибо, Дуг, я внес изменения.
smileingbovine
Вы уверены, что это работает? Мне не удалось связать new Foo()вызов и initобъединить в цепочку, потому что я не мог получить доступ к свойствам объектов. Я должна была бежатьvar a = new Foo(); a.init_1('constructor 1');
Милли Смит
@MillieSmith Признаюсь, я давно не писал JS ... но я просто вставил этот код в консоль Chrome JS, и цепочка от new до init сработала.
laughingbovine
3

Иногда значений по умолчанию для параметров достаточно для нескольких конструкторов. И когда этого недостаточно, я пытаюсь обернуть большую часть функциональности конструктора в функцию init (other-params), которая вызывается впоследствии. Также рассмотрите возможность использования концепции фабрики для создания объекта, который может эффективно создавать другие объекты, которые вы хотите.

http://en.wikipedia.org/w/index.php?title=Factory_method_pattern&oldid=363482142#Javascript

eruciform
источник
1
Фабричный метод кажется хорошим решением - только убедитесь, что не путаете его с использованием отдельного фабричного класса, который, вероятно, совершенно неуместен в данном случае использования.
Simon Groenewolt
1
Эта ссылка никуда не ведет. На этой странице нет привязки Javascript.
Роб
3

Отвечаю, потому что этот вопрос сначала возвращается в Google, но ответы уже устарели.

Вы можете использовать объекты деструктуризации в качестве параметров конструктора в ES6.

Вот шаблон:

У вас не может быть нескольких конструкторов, но вы можете использовать деструктурирование и значения по умолчанию, чтобы делать то, что хотите.

export class myClass {

  constructor({ myArray = [1, 2, 3], myString = 'Hello World' }) {

    // ..
  }
}

И вы можете сделать это, если хотите поддерживать конструктор без параметров.

export class myClass {

      constructor({myArray = [1, 2, 3], myString = 'Hello World'} = {}) {

        // ..
      }
}
DalSoft
источник
2
export default class Order {

    static fromCart(cart) {
        var newOrder = new Order();
        newOrder.items = cart.items;
        newOrder.sum = cart.sum;

        return newOrder;
    }

    static fromOrder(id, order) {
        var newOrder = new Order();
        newOrder.id = id;
        newOrder.items = order.items;
        newOrder.sum = order.sum;

        return newOrder;
    }
}

Useges:

  var newOrder = Order.fromCart(cart)
  var newOrder = Order.fromOrder(id, oldOrder)
Арсен Аблаев
источник
0

Это пример, приведенный для нескольких конструкторов в Программе в HTML5 с помощью JavaScript и CSS3 - Exam Ref .

function Book() {
    //just creates an empty book.
}


function Book(title, length, author) {
    this.title = title;
    this.Length = length;
    this.author = author;
}

Book.prototype = {
    ISBN: "",
    Length: -1,
    genre: "",
    covering: "",
    author: "",
    currentPage: 0,
    title: "",

    flipTo: function FlipToAPage(pNum) {
        this.currentPage = pNum;
    },

    turnPageForward: function turnForward() {
        this.flipTo(this.currentPage++);
    },

    turnPageBackward: function turnBackward() {
        this.flipTo(this.currentPage--);
    }
};

var books = new Array(new Book(), new Book("First Edition", 350, "Random"));
Саймон Стэнфорд
источник
А неизменность? использование прототипа делает свойства общедоступными, верно?
Габриэль Симас
6
Это неверно. Ваше второе определение конструктора переопределяет первое, поэтому, когда вы позже вызываете new Book (), вы вызываете второй конструктор со всеми значениями параметров, установленными на undefined.
ErroneousFatality