Clarifying the Dual Purpose of Functions

In ECMAScript 5 and earlier, functions serve the dual purpose of being callable with or without new. When used with new, the this value inside a function is a new object and that new object is returned, as illustrated in this example:

  1. function Person(name) {
  2. this.name = name;
  3. }
  4. var person = new Person("Nicholas");
  5. var notAPerson = Person("Nicholas");
  6. console.log(person); // "[Object object]"
  7. console.log(notAPerson); // "undefined"

When creating notAPerson, calling Person() without new results in undefined (and sets a name property on the global object in nonstrict mode). The capitalization of Person is the only real indicator that the function is meant to be called using new, as is common in JavaScript programs. This confusion over the dual roles of functions led to some changes in ECMAScript 6.

JavaScript has two different internal-only methods for functions: [[Call]] and [[Construct]]. When a function is called without new, the [[Call]] method is executed, which executes the body of the function as it appears in the code. When a function is called with new, that’s when the [[Construct]] method is called. The [[Construct]] method is responsible for creating a new object, called the new target, and then executing the function body with this set to the new target. Functions that have a [[Construct]] method are called constructors.

I> Keep in mind that not all functions have [[Construct]], and therefore not all functions can be called with new. Arrow functions, discussed in the “Arrow Functions” section, do not have a [[Construct]] method.

Determining How a Function was Called in ECMAScript 5

The most popular way to determine if a function was called with new (and hence, with constructor) in ECMAScript 5 is to use instanceof, for example:

  1. function Person(name) {
  2. if (this instanceof Person) {
  3. this.name = name; // using new
  4. } else {
  5. throw new Error("You must use new with Person.")
  6. }
  7. }
  8. var person = new Person("Nicholas");
  9. var notAPerson = Person("Nicholas"); // throws error

Here, the this value is checked to see if it’s an instance of the constructor, and if so, execution continues as normal. If this isn’t an instance of Person, then an error is thrown. This works because the [[Construct]] method creates a new instance of Person and assigns it to this. Unfortunately, this approach is not completely reliable because this can be an instance of Person without using new, as in this example:

  1. function Person(name) {
  2. if (this instanceof Person) {
  3. this.name = name; // using new
  4. } else {
  5. throw new Error("You must use new with Person.")
  6. }
  7. }
  8. var person = new Person("Nicholas");
  9. var notAPerson = Person.call(person, "Michael"); // works!

The call to Person.call() passes the person variable as the first argument, which means this is set to person inside of the Person function. To the function, there’s no way to distinguish this from being called with new.

The new.target MetaProperty

To solve this problem, ECMAScript 6 introduces the new.target metaproperty. A metaproperty is a property of a non-object that provides additional information related to its target (such as new). When a function’s [[Construct]] method is called, new.target is filled with the target of the new operator. That target is typically the constructor of the newly created object instance that will become this inside the function body. If [[Call]] is executed, then new.target is undefined.

This new metaproperty allows you to safely detect if a function is called with new by checking whether new.target is defined as follows:

  1. function Person(name) {
  2. if (typeof new.target !== "undefined") {
  3. this.name = name; // using new
  4. } else {
  5. throw new Error("You must use new with Person.")
  6. }
  7. }
  8. var person = new Person("Nicholas");
  9. var notAPerson = Person.call(person, "Michael"); // error!

By using new.target instead of this instanceof Person, the Person constructor is now correctly throwing an error when used without new.

You can also check that new.target was called with a specific constructor. For instance, look at this example:

  1. function Person(name) {
  2. if (new.target === Person) {
  3. this.name = name; // using new
  4. } else {
  5. throw new Error("You must use new with Person.")
  6. }
  7. }
  8. function AnotherPerson(name) {
  9. Person.call(this, name);
  10. }
  11. var person = new Person("Nicholas");
  12. var anotherPerson = new AnotherPerson("Nicholas"); // error!

In this code, new.target must be Person in order to work correctly. When new AnotherPerson("Nicholas") is called, the subsequent call to Person.call(this, name) will throw an error because new.target is undefined inside of the Person constructor (it was called without new).

W> Warning: Using new.target outside of a function is a syntax error.

By adding new.target, ECMAScript 6 helped to clarify some ambiguity around functions calls. Following on this theme, ECMAScript 6 also addresses another previously ambiguous part of the language: declaring functions inside of blocks.