11

I know this is not the recommended way of doing it, but if I declare the following functions, and then invoke them as constructors, what will be the difference (if any) between the resulting objects?

function Something() {
    this.foo = "bar";
}

function something2() {
    var that = {};
    that.foo = "bar";
    return that;
}

var x = new Something();
var y = new something2();
var z = something2();

I.e. what will differ between x, y and z here?

Wouldn't something2 be a much better way of writing the constructor, since whether you use new or not will not affect the result of the function?

BTW should something2 be capitalized here? (I assume not since Crockford is so adamant on the capitalization, for functions will clobber the global namespace...)

zrajm
  • 1,361
  • 1
  • 12
  • 21
  • 2
    Be a little wary of Mr Crockford. Whilst he has a lot of good things to say, he does have Opinions which are disagreed with. – staticsan Apr 26 '12 at 06:50

4 Answers4

15

In short:

new something2() instanceof something2 === false

Relatedly, if you extend your example to use the prototype property

Something.prototype.method = function () { };
something2.prototype.method = function () { };

you will find that the prototype is not inherited in the latter case:

typeof (new Something()).method === "function"
type (new something2()).method === "undefined"

The real answer is that you are tapping into entirely different underlying machinery. Calling with new invokes the [[Construct]] mechanism, which involves setting the [[Prototype]] property according to the .prototype property of the constructor.

But a funny thing happens in steps 8--10 of the [[Construct]] algorithm: after setting up a new, empty object, and then attaching its [[Prototype]], it does a [[Call]] to the actual constructor, using this new empty-plus-prototype object as this. And then, in step 9, if it turns out that that constructor returned something---it throws away that prototypally-bound, passed-as-this object that it spent all that time setting up!

Note: you can access an object's [[Prototype]] (which is different from a constructor's .prototype) with Object.getPrototypeOf:

Object.getPrototypeOf(new Something()) === Something.prototype // steps 5 & 6
Object.getPrototypeOf(new something2()) === Object.prototype // default

To answer some meta-questions:

  • No, don't capitalize something2, since it is a factory function and not a constructor. If something is capitalized, it is expected to have constructor semantics, e.g. new A() instanceof A.
  • If you're worried about the danger of clobbering the global namespace, you should start using strict mode, by putting "use strict"; at the top of your files. One of the many nice cleanups of strict mode is that this defaults to undefined, not the global object, so e.g. calling a constructor without new will result in errors the moment the constructor tries to attach properties to undefined.
  • Factory functions (aka the "closure pattern") are in general a reasonable substitute for constructors and classes, as long as you are (a) not using inheritance; (b) not constructing too many instances of that object. The latter is because, in the closure pattern, you attach a new instance of every method to every newly-created object, which is not great for memory usage. The biggest payoff, IMO, of the closure pattern is the ability to use "private" variables (which are a good thing, and don't let anyone tell you otherwise :P).
Community
  • 1
  • 1
Domenic
  • 110,262
  • 41
  • 219
  • 271
  • The 'new' is also optional (or indeed not needed) on the second method, but screws things up when left out in the first (this === window) – Matt Apr 26 '12 at 06:52
  • @Matt—you can defend against that a little from inside the constructor by seeing if `this` is the global object and proceding accordingly. – RobG Apr 26 '12 at 06:58
  • 1
    @Matt, @RobG---or, just put `"use strict";` at the top of your file! Then `this` will be `undefined`. – Domenic Apr 26 '12 at 07:01
  • @Dominic At the top of the function suffices -- but we will have to wait for JScript to keep up. In general, ES5 strict mode is not a safe feature yet, even in non-JScript. – PointedEars Apr 26 '12 at 07:16
  • Why wait for JScript? It'll help catch your errors in development, where you presumably test in more than just IE<=8. And it doesn't hurt anything in production. – Domenic Apr 26 '12 at 07:17
2

In the second case, the returned object doesn't inherit anything from the constructor, so there's little point in using it as such.

> var x = new Something();
> var y = new something2();
> var z = something2();

I.e. what will differ between x, y and z here?

x inherits from Something, wheres neither y or z inherit from something2.

Wouldn't something2 be a much better way of writing the constructor, since whether you use new or not will not affect the result of the function?

There is no point in calling something2 as a constructor because the object it returns isn't the newly constructed object assigned to its this that inherits from something2.prototype, which is what others might expect to get when calling new something2().

BTW should something2 be capitalized here? (I assume not since Crockford is so adamant on the capitalization, for functions will clobber the global namespace...)

No, because calling it as a constructor is a bit pointless, so characterising it as one would be misleading.

RobG
  • 142,382
  • 31
  • 172
  • 209
1

Invoking a function as a constructor (i.e. with the new keyword) runs the following steps:

  1. create a new object
  2. set the prototype of that object to the object in the prototype property of the function
  3. execute the constructor function in the context of that object (i.e. this is the new object)
  4. return that object (if the constructor has no return statement)

So, your second solution will just return a plain object with a property "foo". But neither y nor z are instanceof Something2 and don't inherit from that prototype. There are functions like that, yes, but they should not be called constructors (no uppercase naming, no invokation with new). They belong to the factory pattern.

If you want a constructor which can be executed without new, use that code:

function Something(params) {
    if (! this instanceof Something)
         return new Something(params);
    // else use "this" as usual
    this.foo = "bar";
    ...
 }
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

I'd say the most important thing would be the prototype of the returned objects.

  function Something() {
       this.foo = "bar";
  }

  Something.prototype = {
    // Something prototype code
    hello: function(){
     //...
    }
  }

  function something2() {
     var that = {};
     that.foo = "bar";
     return that;
  }

  something2.prototype = {
      // something2 prototype code
      greetings : function() {
      //...
      }
  }

  var x = new Something();
  var y = new something2();
  var z = something2();

  typeof x.hello === function // should be true
  typeof y.greetings === undefined // should be true
  typeof z.greetings === undefined // should be true

In other words, I'd say you're not instantiating objects withe something2, you are creating purely new objects that inherit from Object.

  1. Something() will give you new objects of "Something" type when you use the new keyword.
  2. something2() will give you new objects of "Object" type, which will immediately return a new empty object.
  3. new something2 is inefficient, because you are creating a blank scope, from which you create a new object

    var that = {};
    

    which is equivalent to

    var that = new Object();
    
Vlad Nicula
  • 3,577
  • 6
  • 32
  • 50