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


124

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


будьте более конкретны, пожалуйста. вам нужны конструкторы с разными наборами параметров?
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, не так уж и много.


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);
};

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');

Итак, в основном вы здесь берете объект 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


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'} = {}) {

        // ..
      }
}

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.