Search code examples
javascriptprototypeecmascript-5

JavaScript: Should a function be able to create instances of itself with Object.create()


My use case is the following: I want to create a factory which produces various kinds of data transfer objects (DTOs). They must be easily serializable and they must have a few additional methods.

My current implementation looks like this (simplified):

window.Dto = function(type, properties)
{
    var
        self = this,
        values = {},
        object = Object.create(self);

    properties.forEach(function(prop){
        Object.defineProperty(object, prop, {
            get: function() { return values[prop]; },
            set: function(value) { values[prop] = value; },
            enumerable: true
        });
    });

    this.getType = function()
    {
        return type;
    };

    this.doSomeMagic = function()
    {
        // ...
    };

    return object;
};

// creating a DTO of the Transport.Motorized.Car class
var carObject = new Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);

(Note: I do not want to create an explicit class for each of these objects, because there are hundets of them, and they are exported from the server side. Also, what you see as properties parameter above, is actually a map of meta data with validation constraints etc.)

I did a quick performance check with a loop where 50,000 of such objects were created. performance.now() tells me that it took a bit more than 1s – which looks ok, but not too impressive.

My question is mainly: Is it ok that the factory creates an instance from its own prototype (if I understand correctly what that code does) and returns it? What side effects can it have? Is there a better way?


Solution

  • As far as I understand factory functions, their whole point is not needing to create new instances of the function itself. Instead, it just returns a newly created object.

    So instead of using instance properties (via this) of the newly created instance (via the new operator), I would just create an object (let's call it factoryProto) and assign all the "instance" methods to that object instead.

    Then, you can use factoryProto as the [[Prototype]] for your new object:

    window.Dto = function(type, properties) {
        var factoryProto = {
              getType: function() {
                return type;
              },
              doSomeMagic: function() {
                  // ...
              }
            },
            values = {},
            object = Object.create(factoryProto);
    
        properties.forEach(function(prop) {
            Object.defineProperty(object, prop, {
                get: function() { return values[prop]; },
                set: function(value) { values[prop] = value; },
                enumerable: true
            });
        });
    
        return object;
    };
    
    // creating a DTO of the Transport.Motorized.Car class
    var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
    

    If you want to fully profit from the prototype-chain, you could define the factoryProto outside of the factory function. To keep track of type, you could add it as a non-enumerable object property:

    window.Dto = (function() {
        var factoryProto = {
            getType: function() {
              return this.type;
            },
            doSomeMagic: function() {
                // ...
            }
        };
    
        return function(type, properties) {
            var values = {},
                object = Object.create(factoryProto);
    
            properties.forEach(function(prop) {
                Object.defineProperty(object, prop, {
                    get: function() { return values[prop]; },
                    set: function(value) { values[prop] = value; },
                    enumerable: true
                });
            });
    
            Object.defineProperty(object, 'type', {
              value: type,
              enumerable: false
            });
    
            return object;
        };
    })();
    
    // creating a DTO of the Transport.Motorized.Car class
    var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);