Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Objects & Prototypes
JavaScript is an object-oriented language, but it uses prototypal inheritance instead of classical inheritance (like Java or C++). Understanding objects and prototypes is essential for mastering JavaScript. In most languages you learn, objects are created from classes — you define a blueprint, then stamp out instances. JavaScript flips this on its head. Here, objects inherit directly from other objects. Think of it like learning a skill from a mentor rather than reading a textbook: there is no abstract blueprint, just a real existing object that a new object can look to for guidance. When the new object cannot find a property on itself, it asks its mentor (prototype). If the mentor does not have it either, the mentor asks its mentor, all the way up the chain. This is prototypal inheritance, and once you internalize it, the entire language makes more sense.1. Objects
An object is a collection of key-value pairs. Keys are strings (or Symbols), values can be anything.Creating Objects
Computed Property Names (ES6)
Shorthand Properties (ES6)
2. The this Keyword
this is one of the most confusing parts of JavaScript. Its value depends on how a function is called, not where it is defined.
Here is the mental model that makes this predictable: look at the call site, not the function definition. When you see obj.fn(), the dot before fn tells you this is obj. When you see fn() with no dot, there is no object, so this falls back to undefined (in strict mode) or window (in sloppy mode). Every time you are confused about this, find the call site and ask: “What is to the left of the dot?”
Rules for this (in priority order)
| Call Type | this Value | Example |
|---|---|---|
new Fn() | The newly created object | new Person('Alice') |
call/apply/bind | Explicitly set | fn.call(obj) |
Method call (obj.fn()) | The object before the dot | person.greet() |
Simple call (fn()) | undefined (strict) or window (sloppy) | greet() |
| Arrow function | Inherits from enclosing scope (always) | () => this.name |
Examples
Arrow Functions and this
Arrow functions do not have their own this. They capture this from the enclosing scope at the time they are defined, not called. This is what makes them ideal for callbacks inside methods.
Binding this
When you need to control this explicitly, JavaScript gives you three tools. Think of them as three ways to hand a function its “context badge” — telling it who it should report to.
call vs apply vs bind — Complete Comparison
| Method | Invokes immediately? | Arguments | Returns | Use when |
|---|---|---|---|---|
fn.call(thisArg, a, b, c) | Yes | Listed individually | Function’s return value | You know the arguments at call time |
fn.apply(thisArg, [a, b, c]) | Yes | As an array | Function’s return value | Arguments are already in an array |
fn.bind(thisArg, a, b) | No | Partially applied (optional) | A new function | You need a reusable bound function (event handlers, callbacks) |
this Decision Guide
When you are confused about what this is, check these rules in order (first match wins):
newkeyword? —thisis the newly created object.call/apply/bind? —thisis whatever you passed as the first argument.- Method call (
obj.fn())? —thisis the object to the left of the dot. - Arrow function? —
thisis whateverthiswas in the enclosing scope when the arrow was defined. - Plain function call (
fn())? —thisisundefined(strict mode) orwindow/globalThis(sloppy mode).
3. Prototypes
Every JavaScript object has a hidden property called[[Prototype]] (accessible via __proto__ or Object.getPrototypeOf()). When you access a property, JavaScript looks up the prototype chain.
Think of the prototype chain like a family tree of knowledge. When you (the object) need to answer a question (access a property), you first check if you know the answer yourself. If not, you ask your parent. If they do not know either, they ask their parent, all the way up to the root ancestor (Object.prototype). If nobody in the chain knows, the answer is undefined.
The Prototype Chain
When accessingrabbit.toString():
- Check
rabbit— Not found - Check
animal— Not found - Check
Object.prototype— Found! (this is why every object hastoString())
Object.create()
The proper way to create an object with a specific prototype.
4. Constructor Functions
Before ES6 classes, constructor functions were the standard way to create objects with shared behavior.The new Keyword
When you call new Person(), JavaScript does five things behind the scenes:
- Creates a new empty object
{} - Sets the new object’s
[[Prototype]]toPerson.prototype - Binds
thisinside the constructor to the new object - Executes the constructor function body
- Returns the new object (unless the constructor explicitly returns a different object)
5. ES6 Classes
Classes are syntactic sugar over constructor functions and prototypes. They do not introduce a new OOP model — under the hood, aclass declaration creates a constructor function with methods on its .prototype, exactly like the manual approach above. The benefit is cleaner syntax and built-in guardrails (like requiring new).
Inheritance with extends
Private Fields (ES2022)
Object Creation Patterns — When to Use Which
| Pattern | Syntax | Inheritance | Private state | new required | Use when |
|---|---|---|---|---|---|
| Object literal | { key: val } | No (unless __proto__) | No (closure trick needed) | No | One-off objects, config, data |
Object.create(proto) | Object.create(animal) | Yes (sets prototype directly) | No | No | Prototypal delegation, dictionary objects |
| Constructor function | function Foo() {} | Yes (via .prototype) | Closure-based only | Yes | Legacy code, pre-ES6 patterns |
| ES6 Class | class Foo {} | Yes (via extends) | Yes (#field) | Yes (enforced) | Modern code — the default choice |
| Factory function | function create() { return {} } | Optional (compose as needed) | Yes (closures) | No | When you want composition over inheritance |
- Simple data objects (API responses, config): Object literals. No ceremony needed.
- Shared behavior via inheritance: ES6 classes. Cleanest syntax, enforces
new, supports#private. - Composition over inheritance: Factory functions. Avoid class hierarchies; compose behavior from small functions.
- Delegation without constructors:
Object.create(). Useful for dictionary-like objects (Object.create(null)creates an object with no prototype — notoString, nohasOwnProperty, nothing inherited).
6. Object Methods
Object.keys/values/entries
Object.assign and Spread
Copying Objects — When to Use Which Method
| Method | Depth | Handles circular refs? | Handles functions? | Handles Dates/RegExp? | Speed |
|---|---|---|---|---|---|
{ ...obj } (spread) | Shallow | N/A | Yes (copies reference) | No (copies reference) | Fastest |
Object.assign({}, obj) | Shallow | N/A | Yes (copies reference) | No (copies reference) | Fast |
structuredClone(obj) | Deep | Yes | No (throws) | Yes (properly clones) | Medium |
JSON.parse(JSON.stringify(obj)) | Deep | No (throws) | No (drops them) | No (Dates become strings) | Slow |
- Flat object, no nesting: Use spread
{ ...obj }. Simplest and fastest. - Nested objects, no functions: Use
structuredClone(). Correct deep copy with circular reference support. - Nested objects with functions: No built-in solution. Use a library like Lodash
_.cloneDeep(), or write a custom recursive clone. - Never use
JSON.parse(JSON.stringify())in production: It silently drops functions, converts Dates to strings, losesundefinedproperties, and throws on circular references. It is only acceptable as a quick hack in debugging.
Object.freeze and Object.seal
| Operation | Object.freeze | Object.seal | Normal object |
|---|---|---|---|
| Modify existing properties | No | Yes | Yes |
| Add new properties | No | No | Yes |
| Delete properties | No | No | Yes |
| Reconfigure properties | No | No | Yes |
| Depth | Shallow only | Shallow only | N/A |
Summary
- Objects: Key-value pairs. Use dot or bracket notation.
this: Depends on how a function is called. Arrow functions inheritthis.- Prototypes: Objects inherit from other objects via the prototype chain.
- Classes: Syntactic sugar over prototypes. Use
extendsfor inheritance. - Private Fields: Use
#prefix for true encapsulation (ES2022).
Interview Deep-Dive
Explain prototypal inheritance to me. How is it fundamentally different from classical inheritance in Java or C++?
Explain prototypal inheritance to me. How is it fundamentally different from classical inheritance in Java or C++?
Strong Answer:
- In classical inheritance (Java, C++), you define a class (a blueprint), and objects are instances stamped from that blueprint. The class hierarchy is static — defined at compile time. A
Dog extends Animalrelationship is baked into the type system. - In prototypal inheritance, there are no classes at the fundamental level (ES6
classis syntactic sugar). Objects inherit directly from other objects. Every object has an internal[[Prototype]]link pointing to another object. When you access a property on an object and it is not found, the engine walks up the prototype chain — checking the prototype, then the prototype’s prototype — until it finds the property or reachesnull. - The key practical difference: prototypal inheritance is dynamic. You can modify a prototype at runtime and all objects that inherit from it immediately see the change. In classical inheritance, adding a method to a base class after compilation requires recompilation. In JavaScript,
Animal.prototype.breathe = function() {}instantly gives every existing animal instance thebreathemethod. This is powerful but dangerous — monkey-patching built-in prototypes (likeArray.prototype) is how libraries historically caused conflicts. - Another difference: prototypal inheritance naturally supports “mixins” and composition. You can copy methods from multiple source objects onto a single target using
Object.assign(target.prototype, mixin1, mixin2). Classical inheritance typically supports single inheritance, requiring interfaces or abstract classes for multiple behavior contracts. - Real-world implication: when you write
class Dog extends Animalin JavaScript, behind the scenes,Dog.prototype.__proto__ === Animal.prototypeistrue. Theextendskeyword sets up the prototype chain. Thesuperkeyword navigates it. Understanding this means you can debug inheritance bugs by inspecting the actual prototype chain withObject.getPrototypeOf()instead of guessing at class hierarchies.
Object.create(null)) has Object.prototype at the top of its prototype chain. If you add Object.prototype.hack = true, then {}.hack, [].hack, new Date().hack, and even (function(){}).hack all return true. This is why modifying Object.prototype is considered one of the most dangerous things you can do in JavaScript. It also breaks for...in loops (the new property becomes enumerable and shows up in every loop) unless you use Object.defineProperty with enumerable: false. Libraries like Prototype.js (early 2000s) did this aggressively and caused conflicts with every other library. Modern best practice: never modify built-in prototypes except for polyfills in specific, controlled environments.Explain the 'this' keyword. Give me a scenario where a senior engineer would get tripped up by it.
Explain the 'this' keyword. Give me a scenario where a senior engineer would get tripped up by it.
Strong Answer:
thisin JavaScript is not determined by where a function is written, but by how it is called (the call site). The four rules, in priority order: (1)newbinding —thisis the newly created object. (2) Explicit binding —call,apply,bindsetthisdirectly. (3) Implicit binding —obj.fn()setsthistoobj. (4) Default binding — standalonefn()getsthis = undefinedin strict mode, orwindow/globalThisin sloppy mode. Arrow functions are the exception: they have nothisof their own and inherit it lexically from the enclosing scope.- Senior engineer trap: destructuring a method from an object. Consider:
const { greet } = person; greet();. This looks clean and is common in modern JavaScript. But it detachesgreetfromperson, sothisinsidegreetis no longerperson. It isundefined(strict mode). This happens constantly in React class components:onClick={this.handleClick}passes the method as a bare function reference, losingthis. The fix is.bind(this)in the constructor or using arrow function class fields. - Another senior trap: passing a method to
setTimeoutorsetInterval.setTimeout(person.greet, 1000)does not callperson.greet()— it stores the function reference and calls it later as a bare function. Samethisloss. Same fix:.bind(person)or wrap in an arrow function() => person.greet(). - The deepest gotcha:
thisin a nested function inside a method. Even if the outer method has the correctthis, a regular function defined inside it gets its ownthis(default binding). This is why theconst self = thispattern existed before arrow functions, and why arrow functions are now the standard for callbacks inside methods.
this when you bind a function twice? Does the second bind override the first?No. bind creates a new function with a permanently fixed this. Calling .bind() again on an already-bound function wraps it in another layer but does NOT override the original binding. const bound1 = fn.bind(objA); const bound2 = bound1.bind(objB); bound2() — this inside fn is still objA, not objB. The second bind creates a wrapper that calls bound1 with objB as this, but bound1 ignores that because it is already hard-bound to objA. This is specified in the ECMAScript spec: a bound function’s [[BoundThis]] cannot be overridden by another bind. The only exception: new can override a bind — new bound1() creates a new object for this, ignoring the bound objA.What does Object.create(null) do, and why would you use it?
What does Object.create(null) do, and why would you use it?
Strong Answer:
Object.create(null)creates an object with absolutely no prototype. Its[[Prototype]]isnull, notObject.prototype. This means it has no inherited properties at all: notoString, nohasOwnProperty, noconstructor, no__proto__getter/setter. It is a truly blank dictionary.- The primary use case is creating a “clean” hash map. When you use a plain object
{}as a dictionary, keys liketoString,constructor,__proto__, andhasOwnPropertyare already “occupied” by inherited properties. If a user-provided key happens to be"constructor"or"__proto__", you get subtle bugs or even security vulnerabilities (__proto__pollution attacks).Object.create(null)eliminates this entire class of bugs. - Real-world usage: Express.js and many routing libraries internally use
Object.create(null)for route lookup tables. V8 also optimizesObject.create(null)objects as “dictionary mode” objects, which can be faster for highly dynamic key access patterns (frequent additions/deletions) compared to regular objects which V8 tries to optimize with hidden classes. - The trade-off: you lose all
Object.prototypemethods.obj.hasOwnProperty("key")throws becausehasOwnPropertydoes not exist on the object. You must useObject.hasOwn(obj, "key")(ES2022) orObject.prototype.hasOwnProperty.call(obj, "key"). You also cannot useobj.toString()— you must handle serialization explicitly. - Since ES6,
Mapis generally the better choice for dynamic key-value lookups because it handles any key type, has no prototype pollution risk, and provides.size, iteration, and better performance for frequent add/delete operations.Object.create(null)is still useful when you need an object (for JSON serialization, for example) but want prototype safety.
Object.prototype (or another prototype) through user-controlled input. The classic vector: a deep merge function that recursively copies properties from untrusted JSON into an object. If the attacker sends {"__proto__": {"isAdmin": true}}, a naive merge copies isAdmin onto Object.prototype. Now every object in the application has isAdmin === true, potentially bypassing authorization checks. Real CVEs have been filed against lodash’s _.merge, jQuery’s $.extend, and many other libraries for exactly this vulnerability. Mitigations: (1) never allow __proto__, constructor, or prototype as keys in user input, (2) use Object.create(null) for lookup tables, (3) use Map instead of plain objects for user-controlled keys, (4) freeze prototypes in security-critical code, (5) validate and sanitize all deeply-merged input.ES6 classes are 'syntactic sugar over prototypes.' Prove it. What do classes compile to under the hood?
ES6 classes are 'syntactic sugar over prototypes.' Prove it. What do classes compile to under the hood?
Strong Answer:
- When you write
class Person { constructor(name) { this.name = name; } greet() { return this.name; } }, the engine creates: (1) a constructor functionPersonwhose body is theconstructormethod, (2)Person.prototype.greet = function() { return this.name; }— methods are placed on the prototype, not on each instance. (3)Person.prototype.constructor = Person— the circular reference that constructors have by convention. - You can verify:
typeof Personis"function".Person.prototype.greetexists.new Person("Alice").__proto__ === Person.prototypeistrue. All of this is identical to the pre-class constructor function pattern. - The differences (not just sugar): (1) Classes enforce
new— callingPerson("Alice")withoutnewthrowsTypeError. Constructor functions silently bindthistowindow/undefined. (2) Class methods are non-enumerable by default (Object.keys(Person.prototype)returns[]). Constructor function prototype methods are enumerable. (3) Class bodies are always in strict mode, even without"use strict". (4) Classes are not hoisted in the same way — they have a TDZ likelet/const. (5)extendsproperly sets up the prototype chain including the constructor’s own prototype (Dog.__proto__ === Animal), enablingstaticmethod inheritance. - The
extendskeyword does two things:Dog.prototype.__proto__ = Animal.prototype(instance method inheritance) andDog.__proto__ = Animal(static method inheritance). The second one is unique to classes — pre-ES6, static method inheritance required manual setup. - Private fields (
#field) are genuinely new — they are not sugar over anything. They use a WeakMap-like internal mechanism that provides hard privacy, not the convention-based_underscorepattern.
class Person {}, you can do Person.prototype.legacyMethod = function() { return "works"; } and new Person().legacyMethod() returns "works". You can also do Object.getPrototypeOf(new Person()) === Person.prototype to inspect the chain. In practice, this interop is how polyfills and legacy code coexist with modern classes. The only caveat: if you overwrite Person.prototype entirely (not just add to it), you break the prototype chain for existing instances and lose the constructor reference. This is true for both classes and constructor functions.