6

I'm trying to understand JavaScript's (or at least V8's) behaviour regarding constructor functions.

I know, JavaScript constructor functions should never return anything (so: undefined).

But consider this JavaScript:

function Thing() { 
  return '';
}
var t = new Thing();
console.log(t, typeof t); // => Thing {} "object"

Now, if you do this:

function Thing() { 
  return { hi: '' };
}
var t = new Thing();
console.log(t, typeof t); // => Object {hi: ""} "object"

And even:

function Thing() { 
  this.a = 'a';
  return { hi: '' };
}
var t = new Thing();
console.log(t, typeof t); // => Object {hi: ""} "object"

So, why does a constructor function in JavaScript return an object, but not a primitive, if you write this kind of code?


This behaviour is also mentioned in this SO answer, but not explained. I've also scrolled over The new Operator part of the ECMAScript specification, and its Construct snipped, but this was not enlightening.

Any hints or knowledge (in plain English, please)?

Community
  • 1
  • 1
BairDev
  • 2,865
  • 4
  • 27
  • 50
  • 2
    Well for one, it's not "wrong and ugly" because it's not doing what you think it is. And why would it return a primitive when it's not a primitive object? – Sterling Archer Feb 29 '16 at 15:20
  • 3
    In a nutshell: if you return a primitive from a constructor, it is ignored. If you return an object, it is used as the resulting object. Does that answer the question? Or are you looking for a rationale for this behaviour? – deceze Feb 29 '16 at 15:20
  • 5
    It behaves this way "because the spec says so". However, I think the "why" explanation that you're looking for may be opinion based really - you'd need to get an answer that shows the language design team's reasoning, anything else is just someone's opinion - for example, here's mine: you've called `new` so you're expecting an object, perhaps the language design team felt that you should always get an object, even if the function returns a primitive, but I have nothing solid to back that reasoning. – James Thorpe Feb 29 '16 at 15:20
  • 2
    Continuing @James' reasoning... a function may return something like `true` or `false` as a side effect, which can safely be ignored; but if a "serious" object is returned, that should probably be the return value for an *object constructor function*. – deceze Feb 29 '16 at 15:22
  • I even did not found the place in the spec, where this behaviour is defined. – BairDev Feb 29 '16 at 15:27
  • 1
    @malte The [`new` operator](http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.2) calls [\[\[Construct\]\]](http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2), which does this. – Oriol Feb 29 '16 at 15:30
  • @Oriol Excerpt from your link: `9. If Type(result) is Object then return result. 10. Return obj.` – Ruan Mendes Feb 29 '16 at 15:33
  • It helps with shimming. `if (!window.foo) window.foo = function() { return { bar: function() { ...} } };` – jib Feb 29 '16 at 15:34
  • 1
    @jib Chicken and egg... You can write it like that because you can write it like that. If you couldn't you wouldn't. :) – deceze Feb 29 '16 at 15:35
  • @deceze there are cases shiming browser objects where I don't see another way to do it, such as: https://github.com/webrtc/adapter/blob/master/src/js/firefox/firefox_shim.js#L58 – jib Feb 29 '16 at 15:45
  • 1
    @jib There's always another way to write it: `window.foo = (function () { .. return Foo; })()`. Or I'm not understanding how this relates to the question. – deceze Feb 29 '16 at 15:49
  • @deceze I can imagine cases where one would need to modify the browser object after creating it, but before returning it: e.g. `window.foo = function() { var org = new mozFoo(); org.bar = x; return org; }` - The relevance is that the JS behavior we're discussing means the `new foo()` pattern is acting both as a constructor and a factory function, which is helpful when shimming what should be "returned". – jib Feb 29 '16 at 16:35

1 Answers1

4

That's because, by definition, the purpose of constructors is producing objects, not primitives:

4.3.4 constructor

function object that creates and initialises objects

Therefore, the [[Construct]] internal method (called via the new operator), checks the type of the value returned by [[Call]]:

13.2.2 [[Construct]]

  1. Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
  2. If Type(result) is Object then return result.
  3. Return obj.

In fact, this is an invariant:

[[Construct]] ( )

  • The Type of the return value must be Object.
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Like the link in your comment. I didn't spot that. Thanks. It does not make much sense in my view - but this is not the place for opinions :) . – BairDev Mar 01 '16 at 07:08