Patrón de JavaScript para múltiples constructores

124

Necesito diferentes constructores para mis instancias. ¿Cuál es un patrón común para eso?

codeholic
fuente
sea ​​un poco más específico, por favor. ¿Quiere constructores con diferentes conjuntos de parámetros?
gblazex
¿Puede tener más de un constructor en Javascript?
Doug Hauf
Sí y no @DougHauf. Sí, porque la respuesta proporcionada por bobince proporciona una forma de ofrecer un comportamiento equivalente. No, porque si quisiera varias funciones de constructor distintas (cada una compartiendo el mismo objeto prototipo), ¿cómo se establecería la propiedad constructora del objeto prototipo (ya que la propiedad constructora solo puede apuntar a una función constructora)?
Moika cumple el
3
Todas estas respuestas son antiguas / no ideales. Soy demasiado vago para escribir hasta una respuesta, pero se puede pasar un objeto en torno a las funciones y los constructores y luego usar las teclas al igual que lo haría con argumentos, por ejemplo: function ({ oneThing = 7, otherThing = defaultValue } = {}) { }. El extra = {}que puse allí es otro truco que aprendí recientemente, en caso de que desee la posibilidad de que el usuario no pase ningún objeto y use todos los valores predeterminados.
Andrew
1
Seguimiento: aquí hay algunas buenas formas de resolver este problema: stackoverflow.com/a/32626901/1599699 stackoverflow.com/a/41051984/1599699 stackoverflow.com/a/48287734/1599699 Me gusta especialmente el último de verdad -constructor de múltiples como soporte, utilizando las funciones de fábrica estáticas como constructores ( return new this();, return new this.otherStaticFactoryFunction();, etc.)!
Andrew

Respuestas:

120

JavaScript no tiene sobrecarga de funciones, incluso para métodos o constructores.

Si desea que una función se comporte de manera diferente según el número y los tipos de parámetros que le pase, tendrá que rastrearlos manualmente. JavaScript felizmente llamará a una función con más o menos que el 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);
}

También puedes acceder arguments como una matriz para obtener más argumentos.

Si necesita argumentos más complejos, puede ser una buena idea poner algunos o todos dentro de una búsqueda de objetos:

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

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

Python demuestra cómo los argumentos predeterminados y con nombre se pueden usar para cubrir la mayoría de los casos de uso de una manera más práctica y elegante que la sobrecarga de funciones. JavaScript, no tanto.

bobince
fuente
2
Gracias, esto es realmente bueno. Yo diría que la segunda opción es útil no solo cuando tiene argumentos complejos, sino también argumentos simples pero difíciles de distinguir, por ejemplo, apoyando MyObj({foo: "foo"})más MyObj({bar: "bar"}). MyObj tiene dos constructores, pero ambos toman un argumento, que es una cadena :-)
Alex Dean
1
¿Puede agregar más al código de ejemplo para este ejemplo en particular?
Doug Hauf
Hola @DougHauf, el libro de Crockford 'JavaScript: The Good Parts' tiene una sección sobre esto llamada 'Especificadores de objetos', muchos ejemplos se refieren a él en línea.
Moika cumple el
29

¿Cómo encuentras este?

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

Foobar.prototype = {
    foobar: null
};

Foobar.fromComponents = function(foo, bar) {
    var foobar = foo + bar;
    return new this(foobar);
};
codeholic
fuente
2
devolver nuevo esto (foobar); no funciona. Cambio al volver nuevo Foobar (foobar); y todo es trabajo correcto.
isxaker
10
No lo entiendo. ¿Puede agregar el código donde realmente lo está usando? ¿Vas a tener que llamar a fromComponents cada vez? Porque eso no es realmente un constructor, sino más bien una función auxiliar. La respuesta de @bobince parece más precisa entonces.
hofnarwillie
1
@hofnarwillie Aunque no es un constructor del todo exacto, al usar un método estático tiene un rendimiento bastante similar, es decir, var foobarObj = Foobar.fromComponents (foo, bar); es todo lo que necesita para crear un nuevo objeto con los argumentos alternativos.
Nathan Williams
21

puede usar la clase con métodos estáticos que devuelven una instancia de esa clase

    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)

con este patrón puedes crear multi constructor

Mahdi Shahbazi
fuente
3
Esta es la mejor respuesta. Los demás recurren a analizar matrices y hacer un montón de trabajo innecesario.
Blane Townsend
13

No tenía ganas de hacerlo a mano como en la respuesta de bobince, así que simplemente quité por completo el patrón de opciones de complemento de jQuery.

Aquí está el constructor:

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

Aquí hay diferentes formas de instanciación:

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

Y esto es lo que hizo:

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]}
Jacob McKay
fuente
También hice el mismo patrón en node.js ahora con: npmjs.com/package/extend
Jacob McKay
10

Yendo más allá con la respuesta de eruciform, puede encadenar su newllamada a su 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');
riendo bovino
fuente
Entonces, básicamente, lo que está haciendo aquí es tomar el objeto Foo y luego llamar a los parámetros init_1 e init_2 con las funciones de prototipo. Si su init_1 e init_2 tienen la palabra función con ellos.
Doug Hauf
¿Tiene que haber un punto y coma después de} en el primer Foo ()?
Doug Hauf
Gracias Doug, hice el cambio.
smilingbovine
¿Estás seguro de que esto funciona? No pude encadenar new Foo()y llamar a initjuntos porque no pude acceder a las propiedades de los objetos. Tuve que corrervar a = new Foo(); a.init_1('constructor 1');
Millie Smith
@MillieSmith Admitiré que no he escrito JS en un tiempo ... pero acabo de pegar este código en la consola Chrome JS y la cadena de nuevo a init funcionó.
smilingbovine
3

A veces, los valores predeterminados de los parámetros son suficientes para varios constructores. Y cuando eso no es suficiente, trato de envolver la mayor parte de la funcionalidad del constructor en una función init (other-params) que se llama después. También considere usar el concepto de fábrica para hacer un objeto que pueda crear efectivamente los otros objetos que desee.

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

eruciforme
fuente
1
El método de fábrica se siente como una buena solución; solo asegúrese de no confundirlo con el uso de una clase de fábrica separada, que probablemente sea completamente irrelevante en este caso de uso.
Simon Groenewolt
1
Ese vínculo no va a ninguna parte. No hay un ancla de Javascript en esa página.
Rob
3

Respondiendo porque esta pregunta se devuelve primero en Google, pero las respuestas ahora están desactualizadas.

Puede utilizar Desestructurar objetos como parámetros de constructor en ES6

Aquí está el patrón:

No puede tener varios constructores, pero puede usar la desestructuración y los valores predeterminados para hacer lo que quiera.

export class myClass {

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

    // ..
  }
}

Y puede hacer esto si desea admitir un constructor 'sin parámetros'.

export class myClass {

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

        // ..
      }
}
DalSoft
fuente
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;
    }
}

Usos:

  var newOrder = Order.fromCart(cart)
  var newOrder = Order.fromOrder(id, oldOrder)
Arsen Ablaev
fuente
0

Este es el ejemplo dado para múltiples constructores en Programación en HTML5 con JavaScript y CSS3 - Examen 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"));
Simon Stanford
fuente
¿Y la inmutabilidad? el uso de prototipos hace que las propiedades sean públicas, ¿verdad?
Gabriel Simas
6
Eso es incorrecto. Su segunda definición de constructor anula la primera, por lo que cuando llame a new Book () más adelante, estará llamando al segundo constructor con todos los valores de los parámetros establecidos en indefinido.
ErroneousFatality