new (and this) in JavaScript - what the keyword actually does

The new keyword in JS looks short, but behind it the engine runs four steps at once - creates an empty object, hooks up this, links it to the function's prototype, and returns the whole object back. Without new, you'd write the same thing by hand with Object.create.
The new keyword is one of those operators in JavaScript that look small and do several things at once. You type new UserCreator('Eva', 9) and you get a ready object with fields, a hooked-up this, and working methods. Except behind that one line sits a four-step (sometimes three-step) recipe the engine runs every time - and without new, you’d have to recreate it by hand with Object.create and a few this writes.
This post is about what new lifts off the developer’s shoulders, how it does it step by step, and why ES2015 classes are the same mechanism underneath, just written more nicely.
#Why new exists - the prototypal nature of JS
Every object in JavaScript holds a link to another object - its prototype.
In code, you read that link via Object.getPrototypeOf(obj), and set it via Object.setPrototypeOf(obj, proto). It used to be written as obj.__proto__ (a property on the object instead of a function call) - that form is discouraged today, but you’ll still meet it in older tutorials, libraries, and courses. The spec calls it [[Prototype]]. Four names - all the same link.
What’s it all for? When you try to read a field from an object and it isn’t there, JavaScript looks into the prototype. If it isn’t there either, it looks into the prototype’s prototype. And so on, until it reaches Object.prototype (beyond that there’s nothing). That chain of prototypes is the prototype chain - and it’s what JS inheritance is built on.
Without new, you’d set those connections up yourself. The cleanest way is Object.create(proto) - it creates a new object whose __proto__ points to the given prototype right away:
const userMethods = {
greet() {
return `hi, I'm ${this.name}`;
},
addOne() {
this.score++;
},
};
function createUser(name, score) {
const newUser = Object.create(userMethods);
newUser.name = name;
newUser.score = score;
return newUser;
}
const u = createUser("Eva", 9);
u.greet(); // "hi, I'm Eva"
u.addOne();
u.score; // 10It works - and it’s exactly what new does under the hood - except every such manual constructor has to be written by yourself. Empty object, set the prototype, write the fields, return. Four lines, in which it’s easy to forget the return and suddenly start returning undefined.
new is a shortcut to that same thing: you tell the engine “take the function I defined, treat it as a constructor, and run those four steps for me.”
#What new does automatically
A call to new SomeFunction(args) runs the function in a special mode in which the engine adds four things:
- Creates a new, empty object - just
{}, no fields or methods. - Links the new object to
SomeFunction.prototype- by setting the internal[[Prototype]]link.SomeFunction.prototypeis just an object where you keep methods shared by all instances of the constructor - the JS engine creates it automatically along with the function. - Runs the constructor body - with
thispointing to the new object. Eachthis.foo = ...inside writes a field directly onto that object. - Returns the new object as the result of
new ...- on its own, withoutreturn. That’s whyconst u = new UserCreator('Eva', 9)hands you the instance asu.
You sometimes see this described as three steps (the points above merge 1 + 4 into “creates and returns”), but the four-step breakdown is clearer: it shows that running the function body is a separate phase wedged between setting the prototype and returning the object.
function UserCreator(name, score) {
this.name = name;
this.score = score;
}
UserCreator.prototype.greet = function () {
return `hi, I'm ${this.name}`;
};
const user1 = new UserCreator("Eva", 9);
user1.greet(); // "hi, I'm Eva"#Step by step: what happens inside new UserCreator('Eva', 9)
Going line by line, as if we were the engine.
- Evaluating
new UserCreator('Eva', 9). The engine seesnew, so it knows to runUserCreatorin constructor mode rather than as a regular function. - A new, empty object is created - say
{}. No fields yet. - That object is linked to
UserCreator.prototype- through the prototype link. Thanks to that, a moment lateruser1.greet()will work - because whengreetisn’t onuser1itself, JavaScript follows that link and finds the function on the prototype. - Inside
UserCreator,thisis set to the new object - and the parametersnameandscoreget the values'Eva'and9from the arguments. - The function body runs.
this.name = nameassigns'Eva'as thenamefield on the new object.this.score = scoreassigns9. - The function ends without
return. Thenew ...expression returns this newly created object.
After all of that, the variable user1 points to the structure:
{
name: 'Eva',
score: 9,
__proto__: UserCreator.prototype, // greet() lives here
}The two own fields come from the constructor arguments, and the link to the shared methods was added by the engine itself.
#Where the greet method actually lives
One effect of new is the separation of fields from methods. Fields sit on the object itself (name, score), because they were written via this.something = ... in the constructor. Methods sit on the function’s prototype - once, for all objects that will ever come from this constructor.
const user1 = new UserCreator("Eva", 9);
const user2 = new UserCreator("Lin", 7);
// fields - each instance has its own
user1.name; // 'Eva'
user2.name; // 'Lin'
// method - one shared, lives on the prototype
user1.greet === user2.greet; // true - both instances reach the same function
user1.greet === UserCreator.prototype.greet; // true - and it's the one from the prototype- name:'Eva'
- score:9
- __proto__:UserCreator.prototype
- greet:ƒ () { ... }
- constructor:ƒ UserCreator
Click any expression below to see where the engine actually finds it.
There’s no duplication. greet exists once, on UserCreator.prototype. Every new call with new creates an object that has a link to that prototype but doesn’t copy the function into itself. Put differently: hundreds of instances = one place in memory holding the methods, not hundreds of copies.
It’s the same mechanism as the Object.create with userMethods at the start - except with new the link is set up automatically, and you keep shared methods on the constructor’s prototype instead of in a separate object alongside.
#Convention: constructors start with a capital letter
Because in JavaScript there’s no separate syntax for “this is a constructor.” A constructor is just a function - and looking at userCreator(...), you can’t immediately tell whether it’s:
const user = userCreator("Eva", 9); // factory function?
const user = new userCreator("Eva", 9); // constructor?That’s why the community agreed: constructor names start with a capital letter (UserCreator, Counter, Date), names of regular functions with a small one. It’s a purely visual signal - “call this function with new, that one without.” A linter (e.g. ESLint with the new-cap rule) can enforce it mechanically.
And what happens if you slip and call a constructor without new?
function UserCreator(name, score) {
this.name = name;
this.score = score;
}
const user = UserCreator('Eva', 9); // no new!
console.log('user:', user); // undefined
console.log('globalThis.name:', globalThis.name); // 'Eva' - leaked into globalThis Without new, the whole mechanism doesn’t happen. The engine doesn’t create a new object, doesn’t link the prototype, doesn’t bind it to this. What happens instead depends on the execution mode:
- Without
'use strict'(a regular script):thispoints to the global object -globalThis, i.e.windowin the browser orglobalin Node.this.name = 'Eva'then writesnameonto the global object, from where it leaks into every other script. - In strict mode (
'use strict'at the top of a file or any ES module):thisisundefined, sothis.name = 'Eva'immediately throwsTypeError: Cannot set properties of undefined.
(Where the value of this comes from in the first place is a separate topic - I cover it in the post on execution context. new just overrides whatever the engine would set by default with a fresh object.)
Either way the result is wrong - and that’s why the PascalCase convention matters so much in the older, “classic” style of JS. Classes, which we’ll get to in a moment, fix this problem at the engine level.
#ES2015: class is the same mechanism
From ES2015 on, you don’t have to write constructors by hand - the class syntax provides a layer over everything we described above. Underneath, the prototype chain still runs and new still performs the same four steps - the way you write it is just tidier.
class UserCreator {
constructor(name, score) {
this.name = name;
this.score = score;
}
greet() {
return `hi, I'm ${this.name}`;
}
}
const user1 = new UserCreator("Eva", 9);
user1.greet(); // "hi, I'm Eva"
// still prototype-based:
typeof UserCreator; // 'function'
Object.getPrototypeOf(user1) === UserCreator.prototype; // true
user1.greet === UserCreator.prototype.greet; // trueWhat class adds on top:
-
Forces the call with
new- if you forget and writeUserCreator('Eva', 9)withoutnew, the engine throwsTypeErrorright away. Without a class, the same slip would pass silently (thiswould leak into the global object or beundefined, as we showed above). A class turns a silent bug into an explicit error at the moment of the call. -
Everything in one block - without a class, you wrote the constructor separately (
function UserCreator(...) { ... }), and each method separately viaUserCreator.prototype.greet = function () { ... }. With a class,constructorand all the methods sit together insideclass UserCreator { ... }- without attaching anything toprototypeby hand. Two more things come along that almost no one wrote with plain functions:static- a field or method attached to the class itself, not to instances. SoUserCreator.maxScoreinstead ofuser1.maxScore- something that concerns “all users in general,” not a specific user.#field- a private field. From outside the class you can’t read or replace it; it’s visible only to code inside the class body.
-
Inheritance via
extendsandsuper- inheritance means: you build a new class on top of an existing one. The new one gets all the fields and methods of the old one, and you only add what makes it different. It’s written like this:class Admin extends UserCreator { constructor(name, score, permissions) { super(name, score); // run UserCreator's constructor with these args this.permissions = permissions; // and add something of your own } }Adminhasname,score, and thegreetmethod fromUserCreatorout of the box, plus its ownpermissions. The wordsuperis a reference to the parent class -super(...)in the constructor runs the parent’s constructor, andsuper.someMethod()in a method calls the parent’s method. Without classes, you had to do the same by hand -Admin.prototype = Object.create(UserCreator.prototype)plusUserCreator.call(this, name, score)inside - and each of those lines was a place for an easy-to-miss mistake.
The mechanics of object creation didn’t change - classes are “built on prototypes” (a literal MDN quote), and new runs the same four steps for them.
But classes also give you a few things you can’t reproduce with a plain function:
- the class body always runs in strict mode - regardless of what the rest of the code looks like;
- the class isn’t hoisted - you can’t reach it before its declaration (TDZ like with
let/const); - private fields
#field- real privacy built into the language, unbypassable from outside; extends/super()- inheritance with a separate model for calling the parent constructor.
So a class isn’t pure syntactic sugar - it’s the prototype chain plus a few things the prototype chain on its own can’t do. If you understand new at the function level, classes start here from the same mechanism - just with extra guarantees.
#Takeaway
It comes down to one thing: new is a shortcut to four steps you’d otherwise have to type out by hand - create an empty object, hook it up to this, link it to the function’s prototype, and return it. Everything else is a consequence of those four steps:
- fields sit on the object itself, methods on its
prototype, thisinside the constructor points to that new instance,- without
new, the whole mechanism doesn’t happen andthisleaks into the global object or is leftundefined, - ES2015 classes are the same engine underneath, just with cleaner syntax and a mandatory
new.
Once new stops looking like a keyword and starts looking like a shorthand for Object.create(proto) plus assigning fields plus returning the object, prototypal inheritance and classes line up into one coherent mechanism instead of three separate ideas.
Comments
Loading comments…