THN Interview Prep

Object-Oriented JavaScript: SDE-3 Reference

This section details prototypal inheritance, execution contexts, constructor mechanics, and memory allocation patterns for objects.


15. Factories and Classes

JavaScript supports two primary patterns for object creation: Factory Functions and ES6 Classes.

Factory Functions (Closure State)

Factory functions construct and return a new object literal. They achieve state privacy by wrapping fields inside closures.

  • Pros: Clear encapsulation (no this context binding issues, private variables cannot be leaked).
  • Cons: High memory footprint. If you create 1,000 instances, 1,000 separate copies of each method are allocated in memory.
function UserFactory(name) {
  let _name = name; // Private state via closure
  return {
    getName: () => _name,
    setName: (val) => { _name = val; }
  };
}

ES6 Classes (Prototype Shared State)

ES6 Classes are syntactic sugar over prototype chains.

  • Pros: Memory efficient. Methods are attached to the prototype once and shared across all instances.
  • Cons: Relies on dynamic execution context (this), which can lose binding during event callbacks.
class UserClass {
  #name; // Native private class field (ES2020)
  
  constructor(name) {
    this.#name = name;
  }
  
  getName() {
    return this.#name; // Shared prototype method
  }
}

16. this, call, apply and bind

The this keyword refers to the execution context of the current scope. Its value is not static; it is determined at runtime based on how a function is invoked.

The Five Binding Rules

  1. Default Binding: If a function is called standalone (e.g. fn()), this resolves to the global object (window in browsers, global in Node). In strict mode ("use strict"), it resolves to undefined.
  2. Implicit Binding: If a function is called as an object method (e.g. obj.method()), this resolves to the containing object obj.
  3. Explicit Binding: Using .call(), .apply(), or .bind() to force this to point to a specific object.
  4. new Binding: When called with new, the function acts as a constructor, and this resolves to the newly created instance.
  5. Lexical Binding (Arrow Functions): Arrow functions do not define their own this. They capture the this value of their enclosing lexical context at creation time.
const obj = {
  name: "Target Object",
  printImplicit() {
    console.log(this.name);
  },
  printLexical: () => {
    console.log(this.name); // Arrow function: 'this' is outer global context -> undefined
  }
};

call vs. apply vs. bind

  • call(thisArg, arg1, arg2, ...): Invokes the function immediately, passing arguments individually.
  • apply(thisArg, [arg1, arg2, ...]): Invokes the function immediately, passing arguments as an array.
  • bind(thisArg, arg1, ...): Returns a new bound function with its context permanently locked, but does not execute it.
function greet(salutation, punctuation) {
  return `${salutation}, my name is ${this.name}${punctuation}`;
}

const person = { name: "Antigravity" };

// Explicit Invocation
console.log(greet.call(person, "Hello", "!"));      // "Hello, my name is Antigravity!"
console.log(greet.apply(person, ["Hello", "!"]));   // "Hello, my name is Antigravity!"

// Context Binding
const boundGreet = greet.bind(person, "Welcome");
console.log(boundGreet("."));                       // "Welcome, my name is Antigravity."

17. new, Constructor, instanceof and Instances

When the new operator is executed on a function, the engine performs the following 4 steps under the hood:

  1. Creates a new empty object in the heap: {}.
  2. Links prototypes: Sets the new object's internal prototype ([[Prototype]] or __proto__) to point to the constructor function's prototype object.
  3. Binds this: Invokes the constructor function with the newly created object bound to this, allocating internal instance properties.
  4. Returns the object: If the constructor returns an object explicitly, that object is returned. Otherwise, the newly created object is returned.
function Person(name) {
  this.name = name;
}

// Emulating 'new' behavior
function customNew(Constructor, ...args) {
  const instance = Object.create(Constructor.prototype); // Steps 1 & 2
  const result = Constructor.apply(instance, args);      // Step 3
  return (typeof result === 'object' && result !== null) ? result : instance; // Step 4
}

The instanceof Operator

instanceof checks if the prototype object of a constructor appears anywhere in the prototype chain of an object.

// prototype chain walk: instance.__proto__ === Constructor.prototype ?
console.log(new Person("A") instanceof Person); // true

18. Prototype Inheritance and Prototype Chain

Unlike classical class-based languages (Java, C++), JavaScript uses Prototypal Inheritance.

Loading diagram…

Prototype Chain Delegation

When accessing a property on an object (e.g. obj.prop), the engine checks:

  1. Does obj have prop as an own property? If yes, return it.
  2. If not, lookup the internal [[Prototype]] link to obj.__proto__ and check if it exists there.
  3. Traverse up the prototype chain recursively.
  4. If it reaches Object.prototype.__proto__ (which points to null), return undefined.
const base = { greeting: "Hello" };
const derived = Object.create(base); // derived delegates to base

derived.name = "Instance"; // Shadowing property: added directly to derived

console.log(derived.name);     // "Instance" (found on self)
console.log(derived.greeting); // "Hello" (delegated up the prototype chain to base)

19. Object.create vs Object.assign

These two operations differ in how they structure prototype linkages.

  • Object.create(proto, propertiesObject): Creates a brand-new empty object and sets its prototype (__proto__) to point directly to proto.
  • Object.assign(target, ...sources): Performs a shallow copy of all enumerable, own properties from one or more source objects to a target object. It triggers getters on source objects and setters on the target object. It does not link prototypes.
const parent = { family: "Developer" };

// Object.create
const child1 = Object.create(parent);
console.log(Object.getPrototypeOf(child1) === parent); // true (Linked prototype)

// Object.assign
const child2 = Object.assign({}, parent);
console.log(Object.getPrototypeOf(child2) === parent); // false (Cloned property, prototype is Object.prototype)

Mark this page when you finish learning it.

Spotted something unclear or wrong on this page?

On this page