Padrão JavaScript para vários construtores


124

Preciso de construtores diferentes para minhas instâncias. Qual é um padrão comum para isso?


seja um pouco mais específico, por favor. deseja construtores com diferentes conjuntos de parâmetros?
Gblazex

Você pode ter mais de um construtor em Javascript?
Doug Hauf

Sim e não @DougHauf. Sim, porque a resposta fornecida por bobince fornece uma maneira de fornecer um comportamento equivalente. Não, porque se você quisesse várias funções construtoras distintas (cada uma compartilhando o mesmo objeto protótipo), como a propriedade construtora do objeto protótipo seria definida (uma vez que a propriedade construtora pode apontar apenas para uma função construtora).
Moika Turns

3
Todas essas respostas são antigas / não são ideais. Eu sou muito preguiçoso para digitar uma resposta, mas você pode passar um objeto em torno de funções e construtores e, em seguida, use as teclas assim como você faria argumentos, por exemplo: function ({ oneThing = 7, otherThing = defaultValue } = {}) { }. O extra que = {}eu coloquei lá é outro truque que aprendi recentemente, caso você queira a possibilidade de o usuário não passar nenhum objeto e usar todos os padrões.
Andrew

1
Acompanhamento: Aqui estão algumas boas maneiras de resolver esse problema: stackoverflow.com/a/32626901/1599699 stackoverflow.com/a/41051984/1599699 stackoverflow.com/a/48287734/1599699 Gosto especialmente do último por verdade multiple-construtor-like apoio, usando funções de fábrica estáticos como construtores ( return new this();, return new this.otherStaticFactoryFunction();, etc.)!
Andrew

Respostas:


120

O JavaScript não possui sobrecarga de função, inclusive para métodos ou construtores.

Se você deseja que uma função se comporte de maneira diferente, dependendo do número e tipos de parâmetros que você passar para ela, será necessário farejá-los manualmente. Felizmente, o JavaScript chamará uma função com mais ou menos do que o número declarado de argumentos.

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

Você também pode acessar argumentscomo um array para obter mais argumentos passados.

Se você precisar de argumentos mais complexos, pode ser uma boa ideia colocar alguns ou todos eles dentro de uma pesquisa de objeto:

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

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

O Python demonstra como argumentos padrão e nomeados podem ser usados ​​para cobrir a maioria dos casos de uso de uma maneira mais prática e graciosa do que a sobrecarga de funções. JavaScript, nem tanto.


2
Obrigado, isso é muito bom. Eu diria que a segunda opção é útil não apenas quando você tem argumentos complexos, mas também argumentos simples, mas difíceis de distinguir, por exemplo, suporte MyObj({foo: "foo"})plus MyObj({bar: "bar"}). MyObj tem dois construtores -, mas ambos argumento Take One, que é uma cadeia :-)
Alex Dean

1
Você pode adicionar mais ao código de exemplo para este exemplo em particular.
Doug Hauf

Olá, @DougHauf, o livro de Crockford 'JavaScript: The Good Parts' tem uma seção sobre esse chamado 'Object Specifiers', muitos exemplos referem-se a ele on-line.
Moika transforma

29

Como você encontra esse?

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

Foobar.prototype = {
    foobar: null
};

Foobar.fromComponents = function(foo, bar) {
    var foobar = foo + bar;
    return new this(foobar);
};

2
retornar novo this (foobar); não funciona Mudo no retorno novo Foobar (foobar); e tudo está funcionando corretamente.
Istaker #

10
Eu não entendo. Você pode adicionar o código onde realmente o está usando? Você vai ter que ligar daComponents toda vez? Porque isso não é realmente um construtor, mas uma função auxiliar. A resposta de @ bobince parece mais precisa então.
hofnarwillie

1
@hofnarwillie Embora não seja exatamente um construtor exato, usando um método estático ele executa um desempenho bastante semelhante, por exemplo, var foobarObj = Foobar.fromComponents (foo, bar); é tudo o que você precisa para criar um novo objeto com os argumentos alternativos.
Nathan Williams

20

você pode usar classe com métodos estáticos que retornam uma instância dessa classe

    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)

com esse padrão, você pode criar vários construtores


3
Esta é a melhor resposta. Os outros recorrem à análise de matrizes e fazem um monte de trabalho desnecessário.
Blane Townsend

13

Não estava com vontade de fazê-lo manualmente, como na resposta de bobince, então apenas retirei completamente o padrão de opções do plugin do jQuery.

Aqui está o construtor:

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

Aqui estão diferentes maneiras de instanciação:

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

E aqui está o que isso fez:

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]}

Eu já rolou o mesmo padrão em node.js também agora com: npmjs.com/package/extend
Jacob McKay

10

Indo mais longe com a resposta da eruciforme, você pode encadear sua newchamada em seu initmétodo.

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

Então, basicamente, o que você está fazendo aqui é pegar o objeto Foo e chamar os parâmetros init_1 e init_2 com as funções de protótipo. Seu init_1 e init_2 devem ter a palavra com eles.
Doug Hauf

tem que haver um ponto e vírgula após o} no primeiro Foo ().
Doug Hauf

Obrigado Doug, fiz a alteração.
Rindobovine

Tem certeza de que isso funciona? Não consegui encadear new Foo()e ligar para o grupo initporque não consegui acessar propriedades nos objetos. Eu tive que corrervar a = new Foo(); a.init_1('constructor 1');
Millie Smith

@ MillieSmith, admito que não escrevo JS há algum tempo ... mas colei esse código no console do Chrome JS e a cadeia de novas para init funcionou.
laughingbovine

3

Às vezes, os valores padrão dos parâmetros são suficientes para vários construtores. E quando isso não é suficiente, tento agrupar a maior parte da funcionalidade do construtor em uma função init (other-params) chamada posteriormente. Considere também usar o conceito de fábrica para criar um objeto que possa efetivamente criar os outros objetos que você deseja.

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


1
O método de fábrica parece uma boa solução - apenas não confunda com o uso de uma classe de fábrica separada, o que provavelmente é completamente irrelevante nesse caso de uso.
Simon Groenewolt

1
Esse link não leva a lugar algum. Não há âncora Javascript nessa página.
Rob

3

Respondendo porque esta pergunta é retornada primeiro no google, mas as respostas estão desatualizadas.

Você pode usar objetos de Reestruturação como parâmetros do construtor no ES6

Aqui está o padrão:

Você não pode ter vários construtores, mas pode usar valores de desestruturação e padrão para fazer o que deseja.

export class myClass {

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

    // ..
  }
}

E você pode fazer isso se desejar oferecer suporte a um construtor 'sem parâmetros'.

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

Este é o exemplo dado para vários construtores em Programação em HTML5 com JavaScript e CSS3 - 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"));

E imutabilidade? usando protótipo coloca propriedades públicas, certo?
Gabriel Simas

6
Isso está incorreto. Sua segunda definição de construtor substitui a primeira; portanto, quando você chamar um novo Book () posteriormente, chamará o segundo construtor com todos os valores de parâmetros definidos como indefinidos.
ErroneousFatality
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.