1

This was a question that originally came up because I was trying to document an ES2015 class with JSDoc, and to document the class's constructor function properly I needed to know the return value and its type, and I realized that I had no idea. In researching it I couldn't find the question being asked on Stack Overflow, so I dug around looking up the details of ES2015 classes, the new operator, and the constructor function. I have found what I think might be the answer, but thought it might be helpful to post the question on SO, along with what I found, and see if others can confirm or have better answers.

To demonstrate the question, suppose one has the following code:

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

var mySquare = new Rectangle(10, 10);
console.log(mySquare);//output: Object { height: 10, width: 10 }

One doesn't ever directly or explicitly call the constructor function, nor explicitly return anything from it, but it is clearly invoked and it must return something. So, what is the return value of the constructor function? And of what type is the return value?

To make things more difficult, one can't simply call the constructor and check the type of the return value, as I tried to do at first:

console.log(typeof mySquare.constructor(11, 11));//output: TypeError: class constructors must be invoked with |new|
console.log(typeof Rectangle.prototype.constructor(12, 12));//output: TypeError: class constructors must be invoked with |new|
kenS
  • 384
  • 1
  • 12
  • Please explain the downvote on the question. This is a legitimate question that I needed an answer to and have attempted to explain and ask clearly – kenS Sep 07 '18 at 05:16

1 Answers1

4

According to MDN's reference on the new operator:

When the code new Foo(...) is executed, the following things happen:

  1. A new object is created, inheriting from Foo.prototype.
  2. The constructor function Foo is called with the specified arguments, and with this bound to the newly created object. new Foo is equivalent to new Foo(), i.e. if no argument list is specified, Foo is called without arguments.
  3. The object returned by the constructor function becomes the result of the whole new expression. If the constructor function doesn't explicitly return an object, the object created in step 1 is used instead. (Normally constructors don't return a value, but they can choose to do so if they want to override the normal object creation process.)

(Emphasis mine.) So it seems that the return value of the constructor function is an object, more specifically an instance of the class.

Further research showed that the return value of the constructor can be overridden by explicitly returning any object from within the constructor function (though non-object return values will be ignored, in which case the default of the newly created object reference is used.) I could not find anything to indicate that ES2015 constructors are any different than constructors prior to ES2015 classes.

This Stack Overflow post was very helpful in researching the above, particularly this article it contained a link to, though since constructor did not appear in the code in either the question nor the article, with my limited understanding of constructors that I had going into researching this they did not seem at first to answer my question. Hopefully this may clarify for others in the same situation.

Edit: The veracity of MDN's information was called into question in comments, so I researched a more definitive source. The ECMA specification says of constructors:

A constructor is an object that supports the [[Construct]] internal method.

The [[Construct]] internal method is then defined as follows:

Signature: (a List of any, Object) → Object

Description: Creates an object. Invoked via the new or super operators. The first argument to the internal method is a list containing the arguments of the operator. The second argument is the object to which the new operator was initially applied. Objects that implement this internal method are called constructors. A function object is not necessarily a constructor and such non-constructor function objects do not have a [[Construct]] internal method.

The spec states that in describing signatures such as the one above, the following convention is used: "If an internal method explicitly returns a value, its parameter list is followed by the symbol “→” and the type name of the returned value."

So, to summarize, based on the ECMA specification as quoted above, yes, constructor functions definitively do in fact return objects.

kenS
  • 384
  • 1
  • 12
  • *"So it seems that the return value of the constructor function is an object, more specifically an instance of the class."* No. From the the 3. point you are quoting: *"Normally constructors don't return a value"* . The constructor is a function (almost) like any other. If it does not have a `return` statement then it doesn't return anything. – Felix Kling Sep 07 '18 at 05:14
  • *"I could not find anything to indicate that ES2015 constructors are any different than constructors prior to ES2015 classes."* Except for being able to call `super` there isn't any difference. `class`es are mainly just syntactic sugar, meaning that they "look" different but don't change how the language works. – Felix Kling Sep 07 '18 at 05:15
  • From the same thing I quoted: "The object returned by the constructor function becomes the result of the whole new expression." That seems to say pretty clearly that the constructor returns an object. "If the constructor function doesn't **explicitly** return an object..." - I think the case being talked about there is when the constructor doesn't contain an explicit return statement. It still returns something, though. Namely the the object. It just does so implicitly. Did you read the linked article? Or SO thread? You will see that your first answer is either incorrect or widely disputed. – kenS Sep 07 '18 at 05:23
  • This kind of confusion and the fact that it is in implicit return that the programmer doesn't have access to unless they override it is exactly why I felt like this question and answer (if it is correct) would be useful. – kenS Sep 07 '18 at 05:25
  • Your statement in your 2nd comment accords with what I found. – kenS Sep 07 '18 at 05:25
  • *"That seems to say pretty clearly that the constructor returns an object."* First of all, MDN is not an authoritative source. It's a wiki that everybody can edit. The authoritative source is the [ECMAScript spec](https://tc39.github.io/ecma262/). The wording on MDN is not super precise. *"The object returned by the constructor function becomes the result of the whole new expression."* should be understood as *"**if** the constructor returns an object ...."* . Since you are quoting the `new` operator, which does not distinguish between "old" style constructors and class constructors, we can... – Felix Kling Sep 07 '18 at 05:31
  • ... easily test this with a normal constructor function: `var obj = {}; function Foo() { return obj; }; function Bar() {}; console.log(Foo(), new Foo(), Foo() === new Foo()); console.log(Bar(), new Bar(), Bar() === new Bar());`. I haven't read that particular question, but I have read the specification and have worked with JavaScript for quite a while. – Felix Kling Sep 07 '18 at 05:31
  • How a `class` `constructor` is evaluated is defined https://www.ecma-international.org/ecma-262/9.0/#sec-runtime-semantics-classdefinitionevaluation, steps 8 to 17. If you inspect these steps you will see that at no point is the source of that function being changed to add an "implicit" return statement, nor is it wrapped in another function that returns something. It is taken "as is". The spec is not necessarily easy to understand, but it is the authoritative source. – Felix Kling Sep 07 '18 at 05:37
  • @FelixKling I appreciate you citing definitive sources, which shows an interest in arriving at the correct answer. But saving people from wading through the spec is exactly what clear, concise questions and answers on SO are for. I'm finding a lot of people disagreeing with what you are saying. Which makes this seem like a pretty valid question to me, regardless of whether you are correct. But your example code also does not show that constructors do not return anything, and I could just as easily cite the spec and say it supports what I've said. Which is not that the constructor is being... – kenS Sep 07 '18 at 05:47
  • Mutated to contain a return statement or being wrapped, but rather that its default behavior is to return something. You should actually look at https://stackoverflow.com/a/37330119/10146245. "A relatively little-known thing about constructor functions is that if they return a non-null object reference, the result of new Constructor is the object the constructor returned, rather than the object new created." – kenS Sep 07 '18 at 05:47
  • @FelixKling I have found the signature of the [[Construct]] internal method. It takes as parameters a list and an object, and returns an object: https://www.ecma-international.org/ecma-262/9.0/#table-6 That's as authoritative as it gets that constructor functions return values, specifically objects. – kenS Sep 07 '18 at 06:05
  • I am not disputing that constructors *can* return an object, if done so explicitly. That's meant by the quote *"A relatively little-known thing about constructor functions is that if they return a non-null object reference, the result of new Constructor is the object the constructor returned, rather than the object new created."*, and confirmed by my example above. – Felix Kling Sep 07 '18 at 06:27
  • *'That's as authoritative as it gets that constructor functions return values, specifically objects."* Not quite. A function `F` has a `[[Construct]]` method alright, but `F` *is not* `[[Construct]]`. I.e. if you call `F` normally, then `[[Construct]]` is not executed. `[[Construct]]` is invoked by `new`. `[[Construct]]` itself calls `F` and *depending on the return value of `F`* (which can be nothing), returns either that return value or the newly created object. That's in steps 11-15 of `[[Construct]]`. – Felix Kling Sep 07 '18 at 06:27
  • In short: `new F` calls `F.[[Construct]]` which creates the new instance and calls `F` (the actual constructor function) itself. Depending on the return value of `F`, `[[Construct]]` either returns the return value of `F` or the newly created instance. – Felix Kling Sep 07 '18 at 06:30
  • However, I can see how one could argue that `[[Construct]]` should be considered being part of `F`, and that `F` (if it is a class) can never be invoked without going through `[[Construct]]` and as such it appears that the function implicitly returns an object. I think I'm just very nitpicky at this point. – Felix Kling Sep 07 '18 at 06:41
  • 1
    Yeah I see the distinction that you are making now, though, and I appreciate having that nuanced understanding of it now even if the distinction is a subtle one that I agree one could debate (I honestly thought you were just trolling or something before lol and am glad that you were just being precise). After I get a chance to read up and make sure I understand I may include that nuance in my answer as well. – kenS Sep 07 '18 at 06:45
  • I'm glad :) To put it yet in another way: Given `class Person { constructor(name) { this.name = name; }}`, `new Person('felix')` will of course always return an object. And `Person` can only be called with `new`. But *if you were* able to vall `Person` *without* `new` (such as `mySquare.constructor(11, 11)` in your example), then you would get back `undefined` (because then `[[Call]]` would be invoked instead of `[[Construct]]`, and the constructor function doesn't have an explicit return value). The "implicit" return of the object comes from `[[Construct]]`, not the function itself. – Felix Kling Sep 07 '18 at 07:03