Objects from Functions: When “return” Overrides “new”

In my write-up on basic Javascript prototypal inheritance, I demonstrated this weird JavaScript overriding feature I noticed between new and return when using functions that can be both factory functions and constructors. My understanding was that the return keyword automatically overrides a new operator. Today I discovered that it’s only true if the return value is an object. If the return value is a primitive (string, boolean, number, null), the new operator overrides the return statement and the function becomes a constructor!

This script I wrote helped me figure it out on my own, but I just found the documentation on MDN right near the top of the new keyword page. It seems to be a weird language feature. Why didn’t JavaScript’s language designers choose to make return automatically override new regardless of whether or not the return value is primitive? Here’s the excerpt from the MDN docs:

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 (not null, false, 3.1415 or other primitive types) 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.)

Here’s my demonstration script. It’s also in my gitlab repo for Learning JavaScript the Hard Way.

#!/usr/bin/env node
// This is a test of how the "return" and "new" keywords can override one
// another in object instantiation.
// "return non-primitive object" overrides "new object"
// "new object" overrides "return primitive value"

function FactoryEgg () {
    this.eggType = '\"I was instantiated in a factory function!\"'
    this.sayType = function() {console.log(this.eggType)};
}

function ConstructorEgg (){
    this.eggType = '\"I was instantiated in a constructor function!\"'
    this.sayType = function() {console.log(this.eggType)};
}

// Chicken can be a factory function if supplied a "factory" argumnet,
//  but it can also be a constructor.
function Chicken (whichway) {
    if (whichway === "factory") {
        // This will override "new Chicken()" constructor
        return new FactoryEgg();
    } else {
        // this gets overridden with "new Chicken()" constructor
        return '\"I am a primitive return value!\"';
    }
}

// set the function's prototype for when it is invoked as a constructor
console.log("> Chicken.prototype = new ConstructorEgg();");
Chicken.prototype = new ConstructorEgg();

console.log("\nThe following chicken eggs come from the same " +
    "\nChicken function to demonstrate how keywords " +
    "\n'new' and 'return' can override one another.");
console.log("\n>>> RETURN OBJECT overrides NEW OBJECT");

console.log("> chelsea = Chicken('factory');");
chelsea = Chicken('factory');
console.log("> console.log(chelsea);");
console.log(chelsea);
console.log("> chelsea.sayType();");
chelsea.sayType();

console.log("\n>>> RETURN PRIMITIVE VALUE");

console.log("> charlotte = Chicken('return primitive without new operator!');");
charlotte = Chicken('return primitive without new operator!');
console.log("> console.log(charlotte)");
console.log(charlotte)

console.log("\n>>> NEW OBJECT overrides RETURN PRIMITIVE VALUE");
console.log("> charlene = new Chicken('new overrides return primitive!');")
charlene = new Chicken('new overrides return primitive!');
console.log("> console.log(charlene);");
console.log(charlene);
console.log("> charlene.sayType();");
charlene.sayType();

and the output:

> Chicken.prototype = new ConstructorEgg();

The following chicken eggs come from the same 
Chicken function to demonstrate how keywords 
'new' and 'return' can override one another.

>>> RETURN OBJECT overrides NEW OBJECT
> chelsea = Chicken('factory');
> console.log(chelsea);
FactoryEgg {
  eggType: '"I was instantiated in a factory function!"',
  sayType: [Function] }
> chelsea.sayType();
"I was instantiated in a factory function!"

>>> RETURN PRIMITIVE VALUE
> charlotte = Chicken('return primitive without new operator!');
> console.log(charlotte)
"I am a primitive return value!"

>>> NEW OBJECT overrides RETURN PRIMITIVE VALUE
> charlene = new Chicken('new overrides return primitive!');
> console.log(charlene);
ConstructorEgg {}
> charlene.sayType();
"I was instantiated in a constructor function!"
 
1
Kudos
 
1
Kudos

Now read this

Designing an Interpreter

The Concept I’m building a toy version of Python based on PunyPy from Learn More Python The Hard Way by Zed Shaw. Mine shall be named QuasiPy. It’s barely a language at all; it has no control flow! Despite its pitiful capabilities,... Continue →