Search code examples
javascriptnew-operatorinstantiation

Surprising instantiation pattern - new keyword used on a wrapper function


Working on Paper.js library, I discovered in the codebase, a way to instantiate a class that I never used before. It is used for example here to instantiate the Shape class.
This instantiation pattern can be simplified to this: given a Ball class and a createBall() method that instantiate it:

function Ball() {}
function createBall() {return new Ball()}

We can, of course, get a ball instance by calling:

var ball = createBall();

But more surprinsingly, we can also get a ball instance by calling (notice the new keyword):

var ball = new createBall();

Or as a more abstract way:

var ball = new function() {return new Ball()};

Since createBall() returns a Ball instance, it gives the impression that we are instantiating the Ball class by using new keyword on an instance of it.

But as we can see in the following code, this is not something allowed and throws an error if we do it manually:

var ball1 = new Ball();
var ball2 = new ball1();
// error: ball1 is not a constructor

Can someone explain me what is the logic behind that ?

Here is a comparative example of different instantiation methods:

// Class
function Ball() {}

// Method creating an instance of the class
function createBall() {
  // Instantiate the class
  var ball = new Ball();
  // Return the instance
  return ball;
}

// Expected: instantiating directly works
var ball1 = new Ball();
console.log('ball1', ball1 instanceof Ball); // outputs true

// Expected: instantiating through the creation method works
var ball2 = createBall();
console.log('ball2', ball2 instanceof Ball); // outputs true

// Unexpected: instantiating like this surprisingly works
var ball3 = new createBall();
console.log('ball3', ball3 instanceof Ball); // outputs true

// Expected: instantiating like this throws an error
var ball4 = new ball1();
// error: ball1 is not a constructor

Edit

After reading @robert-zigmond comment, I discovered another misleading case:

function Dog() {}

function Ball() {
  return new Dog();
}

var instance = new Ball();

console.log('is instance a Ball ?', instance instanceof Ball); // false
console.log('is instance a Dog ?', instance instanceof Dog); // true


Solution

  • ball1 isn't even a function, so that's why you get the error there.

    Any function in Javascript can be called using the new operator - essentially that makes it construct a new object, which is taken as the this reference for the function, and returns it (but only if the function doesn't return an object already - if it does, the constructed object is thrown away).

    See here for exactly what the new operator does: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new#Description

    Your "other misleading case" does seem confusing at first, because instance is defined as new Ball() yet but instance instanceof Ball returns false. This is because, as I said above, the "constructor function" only returns the newly-constructed object if it otherwise returns a value which isn't an object at all. In most functions designed to be used as constructors, there is actually no explicit return value - so the return value is implicitly undefined, and so the newly-constructed object is returned.

    But in this example, the function would otherwise return an object, which is an instance of Dog - so this object ends up as instance. Although created through new Ball(), this was somewhat indirect, it was really created by calling new Dog(), and thus Dog.prototype is in its prototype chain, and not Ball.prototype. This explains the behaviour of the instanceof operator that you observed.