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:
function Person(name) {
this.name = name;
}
var person = new Person("Nicholas");
var notAPerson = Person("Nicholas");
console.log(person); // "[Object object]"
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:
function Person(name) {
if (this instanceof Person) {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
var person = new Person("Nicholas");
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:
function Person(name) {
if (this instanceof Person) {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
var person = new Person("Nicholas");
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:
function Person(name) {
if (typeof new.target !== "undefined") {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
var person = new Person("Nicholas");
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:
function Person(name) {
if (new.target === Person) {
this.name = name; // using new
} else {
throw new Error("You must use new with Person.")
}
}
function AnotherPerson(name) {
Person.call(this, name);
}
var person = new Person("Nicholas");
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.