All articlesJavaScript

Ace Your Next JavaScript Interview: `this`, `new`, Prototypes, Classes (Part 3) ✨

Learn the deeper concepts in JavaScript, such as `this`, `new`, Prototypes, and Classes (7 minutes)

Petar IvanovPetar Ivanov
8 min read
On this page

This is part 3 of the “Ace Your Next JavaScript Interview” series where I cover the fundamentals of JavaScript, sharing my notes which I use to review and prepare for interviews.

In today’s article, I’ll cover other JavaScript concepts, such as this, new, Prototypes, and Classes.

If you missed the previous articles, you can check part 1 and part 2:

The this Keyword

In object-oriented languages like C# or Java, the this keyword refers to the current object of a method or a constructor.

So each time you use this inside a method of a object, it will always refer to the same object.

However, in JavaScript, the this keyword implementation is slightly different, which sometimes causes many problems and bugs, if not understood well.

Let explore some examples.

Example 1: Method Call

TypeScript
const hero = {
  name: 'Aria',
  greet() {
    console.log(`${this.name} says hi`);
  }
};

hero.greet(); // Aria says hi

In this case, everything works as expected.

However, the value of this is not determined by the function that uses it.

Example 2: Detached Method

TypeScript
const { greet } = hero;

greet(); //  says hi

In this case, we get an empty value for this.name because the value of this depends on the call-site.

It doesn’t refer to the function in which it’s used or to it’s scope.

It refers to the object on which the function is being executed.

In the example above, I’ve run the code inside the browser, where this refers to the window object and it doesn’t have a name property.

💡 Pro tip: Always ask: “What object sits on the left side of the dot when the function is being called?”

We can say that **this** is not an author-time binding but a runtime binding.

It is contextual based on the conditions of the function’s invocation.

Apart from the mentioned example from above, there’re some other ways of how to manipulate the value of this.

Let’s explore them as well just for completeness.

Example 3: Arrow Functions

Arrow functions don’t get their own this.

Instead, they capture it from the surrounding scope.

TypeScript
const knight = {
  name: 'Lancelot',
  salute: () => console.log(this.name)
};

knight.salute(); // undefined

Example 4: Explicit Binding

We can explicitly set the value of this when we call a method.

This is called binding because we bind a specific value to this.

TypeScript
function introduce() {
  console.log(`I am ${this.name}`);
}

const elf = { name: 'Legolas' };

introduce.call(elf); // I am Legolas

As you see, in JavaScript, we can use this inside functions, not only classes.


The new Keyword

In object-oriented languages like C# or Java, the new keyword is used to create a new instance of class.

However, in JavaScript, the behavior of the new keyword is slightly different, no matter there’re classes in the language.

In OO languages, using the new keyword will result in calling the constructor method of the class.

In JavaScript, we can simply use a function to create a new object.

TypeScript
function Hero(name) {
    this.name = name;
}

const hero = new Hero("Gandalf");

console.log(hero.name); // "Gandalf"

When we use the new keyword before a function, it will create a new object and bind the this inside the function to the newly created object.

Then, it will return the new object, if the function doesn’t return anything.

If the function returns an object, the above is not valid.

TypeScript
function Person(name) {
  this.name = name;
  return { nickname: `The ${this.name}` };
}

const p = new Person("T-Shaped Dev");

console.log(p.nickname); // "The T-Shaped Dev"
console.log(p.name); // undefined

Note: Arrow functions can’t be used as constructors. They throw if you use new.


Note On Using this And new

If you don’t want the headache of using this and new, you might prefer a more functional approach.

You can use closures to deal with this and factory functions to deal with new.

For instance:

TypeScript
// Functional factory + Closure approach
function createCounter(start = 0) {
  let count = start;

  function increment() {
    count += 1;
    console.log(count);
  }

  function reset() {
    count = 0;
    console.log(count);
  }

  return { increment, reset };
}

const c2 = createCounter(5);

c2.increment(); // 6
c2.reset();     // 0

Prototypes

Objects in JavaScript have a prototype property which references another object.

So in JavaScript, we can build an inheritance object hierarchy through using Prototypes.

The other way of building such hierarchies is through Classes which are a syntax sugar over Prototypes.

Whenever you read a property of an object, it will first try to find it in the object itself.

If not found inside the object, it will look for it into the prototype.

If it’s not found there as well, it will check the prototype’s prototype and so on until it reaches the Object.prototype.

TypeScript
const hero = {
  salute: () => console.log('Salute!')
};

const knight = {
  name: 'Knight',
  __proto__: hero
};

knight.salute();

In this case, we set the __proto__ property explicitly for the specific object.

To set up the inheritance chain, we can do it in the constructor function so every created object has the correct prototype.

TypeScript
function Hero(name) {
    this.name = name;
    this.salute = function () {
        console.log(`${this.name}, Salute!`);
    }
}

function Knight(name) {
    this.name = name;
}

// create a reference for the prototype
Knight.prototype = new Hero();

const knight = new Knight("Lancelot");

knight.salute(); // Lancelot, Salute!

By setting the prototype object to the constructor function, we ensure that all objects created through the new keyword will set up properly.

Note: There are some other quirks around Prototypes in JavaScript, but I’ve found the shared notes above to be fundamental.


Classes

ES6 introduced Classes which are a syntax sugar over Prototypes.

Nowadays, using Prototypes is discouraged because of the high complexity around setting up big inheritance hierarchies.

It’s most common to use Classes also because of the familiarity of people with other OO languages.

TypeScript
class Hero {
  constructor(name) {
    this.name = name;
    this.salute = function () {
      console.log(`${this.name}, Salute!`);
    };
  }
}

class Knight extends Hero {
  constructor(name) {
    super(name)
  }
}

const knight = new Knight("Lancelot");

knight.salute(); // Lancelot, Salute!

Composition

Inheritance is a powerful tool to extend entities and create more complex object but it’s not ideal for all use cases.

Creating complex inheritance hierarchies is not trivial and most of the time we might end up with unnecessary complexity.

Personally, I think that the best way to create complex objects in JavaScript is through composition.

This way, we create objects with only what they need, they combine them to create a more complex ones.

TypeScript
function canAttack(state) {
  return {
    attack: () => console.log(`${state.name} slashes!`)
  };
}

function canHeal(state) {
  return {
    heal: () => console.log(`${state.name} heals!`)
  };
}

function createPaladin(name) {
  const state = { name };
  return {
    ...canAttack(state),
    ...canHeal(state),
    name
  };
}

const paladin = createPaladin('Theron');

paladin.attack(); // Theron slashes!
paladin.heal();   // Theron heals!

Performance Considerations About Inheritance

It’s important to mention that if performance is critical to the app then defining a function to the prototype might be a better option, instead of creating a fresh new copy every time.

This means that the function will be created once and will leave on the shared prototype.

TypeScript
function FastFighter(name) {
  this.name = name;

  // per-instance method
  this.attack = () => console.log(`${this.name} strikes fast!`);
}

// vs.

FastFighter.prototype.attack = function() {
  console.log(`${this.name} strikes fast!`);
};

This can be helpful in some scenarios but might lead to problems if not used properly because we deal with references, instead of actual copies.


📌 TL;DR

  • this is set by the call-site, not where a function lives.
  • new creates a fresh new object, binds this, and returns it.
  • Prototypes forms a chain, so objects can inherit missing properties.
  • Classes are a syntax sugar over Prototypes.
  • Prefer composition to mix small behaviors, instead of deep and large hierarchies.
  • We can share code through using prototype methods, boosting performance.

Related articles

Whenever you’re ready, here’s how I can help you:

  1. 1.

    The Conscious React: React architecture, design & clean code — 100+ production tips across 6 chapters, updated for React 19, plus 4 companion repos you can clone and run.

  2. 2.

    The Conscious Node: Node.js architecture, design & clean code — 157 production tips across 10 chapters, from module boundaries to the transactional outbox and zero-downtime deploys.

  3. 3.

    The JavaScript Architect Bundle: Both books + all React companion repos + CLAUDE.md rulesets + both playbooks. The complete path from developer to architect.

  4. 4.

    Free Resources: Architecture playbooks, cheat-sheets, and the JavaScript Architect Roadmap — practical guides for leveling up to senior.

The T-Shaped Dev

Join 30K+ engineers leveling up to architect

One practical tip on JavaScript, React, Node.js, and software architecture every week. No spam, unsubscribe anytime.

Petar Ivanov

Written by

Petar Ivanov

Software engineer, author, and speaker. I help JavaScript developers grow from Mid → Senior → Architect — production-grade React, Node.js, and AI systems.