5

Is there any difference in how these functions operate? The first one is more typically of what I think about when thinking of a constructor.

Example 1: using this to name and set properties. Then using new to create a new Book object.

    function Book(name, numPages) {
        this.name = name;
        this.numPages = numPages;
    }

    var myBook = new Book('A Good Book', '500 pages');

Example 2: returning a object by using new and just calling the function itself.

    function Movie(name, numMinutes) {
        return { name:name, numMinutes:numMinutes };
    }

    var best = new Movie('Forrest Gump', '150');

    var other = Movie('Gladiator', '180');

I guess what I'm trying to figure out is if these are different in the way they create an object? If so is one better than the other? Are there different situations where one would work better over the other?

  • 2
    returning a literal doesn't provide inheritance via `.prototype` props on the constructor function. if you want inheritance, use "new "+constructors. if not, use factories, like your 2nd code. – dandavis Apr 04 '16 at 22:18
  • 2
    In your second snippet, the `new` is just ignored. An empty object that inherits from `Movie.prototype` is created unnecessarily. Just don't use `new` with factory functions. And no, they don't work the same. – Bergi Apr 04 '16 at 22:22
  • You shouldn't use the second one. There you are not inheriting from Movie.prototype anymore and it is for example `other/best instanceof Movie // returns false`. I recommend you to read this: https://zetafleet.com/blog/2014/12/back-to-basics-javascript-functions-constructors-and-this-demystified.html#ref-5 – Kiechlus Apr 04 '16 at 22:23
  • 2
    Possible duplicate of [Constructor function vs Factory functions](http://stackoverflow.com/questions/8698726/constructor-function-vs-factory-functions) – Mike Cluck Apr 04 '16 at 22:33
  • @dandavis: are you suggesting that a factory cannot create new objects using the constructor? – symcbean Apr 04 '16 at 22:49
  • 1
    "Better" needs criteria by which to evaluate and compare approaches. The two approaches are certainly different with different outcomes, which is better depends on your requirements. – RobG Apr 04 '16 at 23:14

3 Answers3

2

The first one is a constructor, and can therefore be extended by a prototype, and you can test via instanceof wether the result is an Instance of this type. Downside: if you forget the new-keyword your code will blow up (unless you write a workaround for that into each constuctor)

And you can't really use apply() with a constructor to pass an array of arguments, when you instantiate a new Object; on the other hand, don't do that, even if you can/could.

The second one is a factory, not a constructor. Independant wether you use the new-keyword or not. with this implementation it creates Objects that look the same but don't share a type or prototype (although the underlying JS-engine recognizes them as similar and so they share the same hidden Class as long as they have the same properties, added in the same order, ... different topic)
long story short, neither performance nor memory-footprint suffer from this approach (anymore)

But you can't check wether they are of the same type, and you don't have a shared prototype that may affect all instances (maybe a pro or a con.)

My goto-approach If I need inheritance, is kind of a mix of both:
(if I just need a data-object I usually use a factory and plain objects).

function Book(conf) {
    var book = Object.create(Book.prototype);
    //a book like this usually has multiple configs/properties
    if(typeof conf === "object"){
        for(var k in conf) book[k] = conf[k];
    }else if(conf){
        //assuming that I get at least the name passed
        book.name = String(conf);
    }
    return book;
}

//I have a prototype that can be extended
//with default-values for example; no idea for a good method 
//to add to the prototype in this example ;)
Book.prototype.numPages = 0;

//but I can also use it like a plain function; no error if you 
var myBook1 = Book("Peter Pan");
var myBook2 = Book({
    name: "American Gods",
    author: "Neil Gaiman"
});

If I add the following line to the top of the function I can also use that as a method to cast anything into an Instance of Book without cloning already existing instances

function Book(conf) {
    //with this simple line I can also use this as a function to cast anything into a "Book"
    if(conf instanceof Book) return conf;

    var book = Object.create(Book.prototype);
    //...
    return book;
}

var data = [
    "Peter Pan",
    {name: "American Gods"},
    //...
];

var books = data.map(Book);

In my opinion, I have the benefits of both worlds with this approach.

Thomas
  • 3,513
  • 1
  • 13
  • 10
0

Basically, when you use new, the JS engine makes a brand new object for you and injects that as the value of this. It also automatically gives you any methods attach to the prototype of the constructor. Using a constructor also allows you to check if an object is an instanceof something more easily.

function MovieA(title) {
    this.title = title;
}
MovieA.prototype.getTitle = function() {
    return this.title;
};

function MovieB(title) {
    return {
    title: title
  };
}
MovieB.prototype.getTitle = function() {
    return this.title;
};

var a = new MovieA('A');
console.log(a instanceof MovieA); // true
console.log(a.getTitle()); // 'A'

var b = MovieB('B');
console.log(b instanceof MovieB); // false
console.log(b.getTitle()); // Uncaught TypeError: b.getTitle is not a function

Everything that new offers you can be attained through other methods but requires more manual labor.

The second method, factories, tend to work better for unit testing, custom object creation and functional programming. It works better for unit testing because if you have a factory producing all of your objects, you can just replace that factory with a mock-up to test different cases.

var Factory = {
  makeThing: function() {
    return { name: 'thing' };
  }
};

// Want to test the case for if makeThing fails inside of some other code
var MockFactory = {
  makeThing: function() {
    return null;
  };
};

As for when you use either, it all depends. Some people don't use new at all. Others exclusively use new. It all depends on if you need any of the things listed above, how much control you need over the creation of objects, when you want to use this or not, etc. In the end, it's all a matter of preference.

Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
0

The difference is the constructor used to create the object returned.

new Book('A Good Book', '500 pages');

creates a Book object instance, with the instance inheriting properties from Book.prototype, including a constructor property value of Book. The Book.prototype object itself inherits from Object.prototype.

var other = Movie('Gladiator', '180');

uses Movie as a factory function (new not required) and returns an Object object instance, with the instance inheriting properties directly fromObject.prototype, including a constructor property value of Object.

More briefly stated, Object literal syntax creates an Object object.

traktor
  • 17,588
  • 4
  • 32
  • 53