JavaScript's new keyword is one of those concepts that seems simple on the surface but hides a powerful and elegant mechanism underneath. Whether you're building a small utility or a large-scale application, understanding how new works is fundamental to mastering object-oriented programming in JavaScript.
In this article, we'll break down exactly what happens when you use new, how constructor functions operate, and the relationship between constructors and the objects they create.
What Does the new Keyword Do?
At its core, new is an operator that creates an instance of a user-defined object type or a built-in object type that has a constructor function. But what actually happens under the hood?
When you write:
JavaScript
Copy
const person = new Person('Alice', 30);
JavaScript performs four distinct steps:
Creates a new empty object
Links that object to the constructor's prototype
Binds
thisto the new object and executes the constructorReturns the new object
Let's explore each step in detail.
Constructor Functions
A constructor function is essentially a regular function designed to be called with new. By convention, constructor names start with a capital letter to distinguish them from regular functions.
JavaScript
Copy
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old.`);
};
}
Here, Person is a constructor function that initializes name and age properties on the new object. When called with new, it sets up the object's initial state.
Key Characteristics of Constructors:
They use
thisto refer to the new object being createdThey don't explicitly return a value (unless you want to override the default behavior)
They are meant to be called with
new
The Object Creation Process: Step by Step
Let's trace through what happens when we execute:
JavaScript
Copy
const alice = new Person('Alice', 30);
Step 1: Create a New Empty Object
JavaScript first creates a new, empty object:
JavaScript
Copy
const alice = {};
Step 2: Link the Object to the Prototype
The new object's internal [[Prototype]] link (accessible via __proto__ or Object.getPrototypeOf()) is set to the constructor's prototype property.
JavaScript
Copy
alice.__proto__ = Person.prototype;
This is the critical step that enables prototypal inheritance. Any methods or properties defined on Person.prototype will now be available to alice.
Step 3: Bind this and Execute the Constructor
The constructor function is called with this bound to the new object:
JavaScript
Copy
Person.call(alice, 'Alice', 30);
Inside the constructor, this.name = name becomes alice.name = 'Alice', and so on.
Step 4: Return the New Object
Finally, the new object is returned:
JavaScript
Copy
return alice;
Note: If the constructor explicitly returns an object, that object will be returned instead. If it returns a primitive value, it's ignored and the new object is still returned.
How new Links Prototypes
The prototype linkage is what makes JavaScript's object system so flexible. Let's see it in action:
JavaScript
Copy
function Animal(name) {
this.name = name;
}
// Add a method to the prototype
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
};
const dog = new Animal('Rex');
dog.speak(); // "Rex makes a sound."
Even though dog doesn't have its own speak method, JavaScript finds it on Animal.prototype through the prototype chain.
Visualizing the Relationship:
plain
Copy
dog (instance)
├── name: "Rex"
└── __proto__ ──► Animal.prototype
├── speak: function()
└── __proto__ ──► Object.prototype
├── toString: function()
└── ...
This chain allows instances to share methods efficiently without each object carrying its own copy.
Instances Created from Constructors
Every object created with new is called an instance of that constructor. You can verify this relationship:
JavaScript
Copy
function Car(model) {
this.model = model;
}
const tesla = new Car('Model S');
console.log(tesla instanceof Car); // true
console.log(tesla instanceof Object); // true
console.log(tesla.constructor === Car); // true
Checking the Prototype Chain:
JavaScript
Copy
console.log(Object.getPrototypeOf(tesla) === Car.prototype); // true
console.log(tesla.__proto__ === Car.prototype); // true
These checks confirm that tesla is properly linked to Car.prototype.
A Practical Example: Building a Simple Library
Let's put it all together with a more practical example:
JavaScript
Copy
function Book(title, author, year) {
this.title = title;
this.author = author;
this.year = year;
}
// Shared method on the prototype (memory efficient!)
Book.prototype.getSummary = function() {
return `${this.title} by ${this.author}, published in ${this.year}`;
};
// Static method on the constructor itself
Book.getOldest = function(books) {
return books.reduce((oldest, book) =>
book.year < oldest.year ? book : oldest
);
};
// Create instances
const book1 = new Book('The Great Gatsby', 'F. Scott Fitzgerald', 1925);
const book2 = new Book('1984', 'George Orwell', 1949);
const book3 = new Book('To Kill a Mockingbird', 'Harper Lee', 1960);
console.log(book1.getSummary());
// "The Great Gatsby by F. Scott Fitzgerald, published in 1925"
const oldest = Book.getOldest([book1, book2, book3]);
console.log(oldest.title); // "The Great Gatsby"
Notice how:
getSummaryis shared across all instances via the prototypegetOldestis a static method available on the constructor itself, not instancesEach instance has its own
title,author, andyearproperties
Common Pitfalls
Forgetting new
If you call a constructor without new, this refers to the global object (window in browsers, global in Node.js) in non-strict mode, or undefined in strict mode.
JavaScript
Copy
function User(name) {
this.name = name;
}
const user = User('Bob'); // Oops! Forgot 'new'
console.log(user); // undefined
console.log(global.name); // "Bob" (in Node.js) or window.name in browser
Solution: Use strict mode or ES6 classes, which throw an error if called without new.
JavaScript
Copy
function User(name) {
'use strict';
this.name = name;
}
// User('Bob'); // TypeError: Cannot set property 'name' of undefined
Arrow Functions as Constructors
Arrow functions cannot be used as constructors because they don't have their own this binding:
JavaScript
Copy
const BadConstructor = () => {
this.value = 42;
};
// new BadConstructor(); // TypeError: BadConstructor is not a constructor
Modern Alternative: ES6 Classes
While this article focuses on constructor functions, it's worth noting that ES6 class syntax is essentially syntactic sugar over the prototype system we've explored:
JavaScript
Copy
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
const person = new Person('Alice', 30);
Under the hood, this still uses prototypes and the same new mechanism. Understanding constructor functions gives you a deeper appreciation of how JavaScript's object system works.
Summary
Table
| Step | What Happens |
|---|---|
| 1 | Create a new empty object |
| 2 | Link it to the constructor's prototype |
| 3 | Bind this to the new object and run the constructor |
| 4 | Return the new object |
The new keyword is the gateway to JavaScript's object-oriented capabilities. By understanding the object creation process, prototype linkage, and the relationship between constructors and instances, you can write more efficient, maintainable, and bug-free code.
Whether you're working with traditional constructor functions or modern ES6 classes, the underlying principles remain the same. Master these fundamentals, and you'll have a solid foundation for advanced JavaScript patterns.
Top comments (0)