Skip to content

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

Profile photo of Adrian Zawadzki

Adrian Zawadzki

17 min read

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; // 10

It 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:

  1. Creates a new, empty object - just {}, no fields or methods.
  2. Links the new object to SomeFunction.prototype - by setting the internal [[Prototype]] link. SomeFunction.prototype is just an object where you keep methods shared by all instances of the constructor - the JS engine creates it automatically along with the function.
  3. Runs the constructor body - with this pointing to the new object. Each this.foo = ... inside writes a field directly onto that object.
  4. Returns the new object as the result of new ... - on its own, without return. That’s why const u = new UserCreator('Eva', 9) hands you the instance as u.

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.

  1. Evaluating new UserCreator('Eva', 9). The engine sees new, so it knows to run UserCreator in constructor mode rather than as a regular function.
  2. A new, empty object is created - say {}. No fields yet.
  3. That object is linked to UserCreator.prototype - through the prototype link. Thanks to that, a moment later user1.greet() will work - because when greet isn’t on user1 itself, JavaScript follows that link and finds the function on the prototype.
  4. Inside UserCreator, this is set to the new object - and the parameters name and score get the values 'Eva' and 9 from the arguments.
  5. The function body runs. this.name = name assigns 'Eva' as the name field on the new object. this.score = score assigns 9.
  6. The function ends without return. The new ... 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
user1 = new UserCreator('Eva', 9)
  • name:'Eva'
  • score:9
  • __proto__:UserCreator.prototype
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): this points to the global object - globalThis, i.e. window in the browser or global in Node. this.name = 'Eva' then writes name onto 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): this is undefined, so this.name = 'Eva' immediately throws TypeError: 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; // true

What class adds on top:

  • Forces the call with new - if you forget and write UserCreator('Eva', 9) without new, the engine throws TypeError right away. Without a class, the same slip would pass silently (this would leak into the global object or be undefined, 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 via UserCreator.prototype.greet = function () { ... }. With a class, constructor and all the methods sit together inside class UserCreator { ... } - without attaching anything to prototype by 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. So UserCreator.maxScore instead of user1.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 extends and super - 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
      }
    }

    Admin has name, score, and the greet method from UserCreator out of the box, plus its own permissions. The word super is a reference to the parent class - super(...) in the constructor runs the parent’s constructor, and super.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) plus UserCreator.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,
  • this inside the constructor points to that new instance,
  • without new, the whole mechanism doesn’t happen and this leaks into the global object or is left undefined,
  • 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…

Leave a comment