|$ curl https://forge-ai.dev/api/markdown?path=docs/js/objects
$cat docs/javascript-—-objects-&-prototypes.md
updated This week·30 min read·published

JavaScript — Objects & Prototypes

JavaScriptObjectsPrototypesIntermediateIntermediate to Advanced
Introduction

Objects are the fundamental building blocks of JavaScript. Almost everything in JavaScript is an object or behaves like one — arrays, functions, dates, regular expressions, and even primitive values get temporarily wrapped in objects when you access properties on them. Understanding objects and the prototype system is the key to mastering JavaScript.

Unlike class-based languages such as Java or C++, JavaScript uses a prototypal inheritance model. Objects inherit directly from other objects through a chain of prototypes. ES2015 introduced class syntax, but it remains syntactic sugar over the prototypal inheritance system — the underlying mechanism has not changed.

This page covers object literals, property descriptors, prototypal inheritance, the prototype chain, object creation patterns, and modern object methods. By the end, you will understand how JavaScript objects truly work under the hood.

objects-intro.js
JavaScript
1// Objects are collections of key-value pairs
2const user = {
3 name: "Alice",
4 age: 30,
5 role: "admin",
6 greet() {
7 console.log(`Hello, I'm ${this.name}`);
8 },
9};
10
11// Everything is an object (almost)
12console.log(typeof {}); // "object"
13console.log(typeof []); // "object"
14console.log(typeof new Date()); // "object"
15console.log(typeof /regex/); // "object"
16console.log(typeof function(){}); // "function" (but callable object)
17
18// Primitives are wrapped temporarily
19console.log("hello".toUpperCase()); // "HELLO" — string wrapped in String object
20console.log((42).toString()); // "42" — number wrapped in Number object
21
22// Prototype chain in action
23const arr = [1, 2, 3];
24console.log(arr.toString()); // "1,2,3" — from Array.prototype
25console.log(arr.hasOwnProperty(0)); // true — from Object.prototype
26// arr → Array.prototype → Object.prototype → null
Object Literals

Object literals (curly braces ) are the most common way to create objects. ES2015 added several enhancements: shorthand property names, computed property keys, method definitions, and spread properties.

object-literals.js
JavaScript
1// Basic object literal
2const person = {
3 firstName: "John",
4 lastName: "Doe",
5 age: 25,
6};
7
8// Property shorthand — variable name becomes key
9const name = "Alice";
10const role = "admin";
11const user = { name, role };
12console.log(user); // { name: "Alice", role: "admin" }
13
14// Method shorthand
15const api = {
16 get(url) {
17 return fetch(url);
18 },
19 async post(url, data) {
20 const res = await fetch(url, {
21 method: "POST",
22 body: JSON.stringify(data),
23 });
24 return res.json();
25 },
26};
27
28// Computed property keys — dynamic property names
29const key = "dynamicKey";
30const obj = {
31 [key]: "computed value",
32 [`prop_${Date.now()}`]: "timestamped",
33 ["method_" + "name"]() {
34 return "dynamic method";
35 },
36};
37console.log(obj.dynamicKey); // "computed value"
38
39// Spread properties — merge objects (shallow)
40const defaults = { theme: "dark", lang: "en", debug: false };
41const overrides = { lang: "fr", debug: true };
42const config = { ...defaults, ...overrides };
43console.log(config); // { theme: "dark", lang: "fr", debug: true }
44
45// Spread for immutability — creating copies
46const original = { a: 1, b: { c: 2 } };
47const copy = { ...original };
48copy.a = 99;
49copy.b.c = 42;
50console.log(original.a); // 1 (not affected — shallow)
51console.log(original.b.c); // 42 (affected — nested objects shared!)
52
53// Nested property access with destructuring
54const data = {
55 user: { profile: { name: "Bob", age: 28 }, settings: { theme: "light" } },
56};
57const { user: { profile: { name, age }, settings: { theme } } } = data;
58console.log(name, age, theme); // "Bob" 28 "light"
59
60// Rest properties — collect remaining properties
61const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
62console.log(a); // 1
63console.log(b); // 2
64console.log(rest); // { c: 3, d: 4 }
65
66// Property existence check
67console.log("toString" in {}); // true (inherited)
68console.log({}.hasOwnProperty("toString")); // false (own only)
69console.log(Object.hasOwn({}, "toString")); // false (modern way)
70
71// Object Sealing and Freezing
72const sealed = Object.seal({ x: 1 }); // can't add/remove, can modify
73sealed.x = 2; // OK
74sealed.y = 3; // ignored (or TypeError in strict)
75delete sealed.x; // ignored
76
77const frozen = Object.freeze({ x: 1 }); // completely immutable
78frozen.x = 2; // ignored (or TypeError in strict)
79console.log(frozen.x); // 1

info

Object spread (...) does a shallow copy. Nested objects are shared between the original and the copy. For deep cloning, use structuredClone(obj) (available in modern environments) or a library like Lodash's cloneDeep. The spread operator is also the cleanest way to do immutable updates: {...obj, key: newValue}.
preview
Property Descriptors

Every property in JavaScript has a property descriptor — a set of attributes that define its behavior. These attributes control whether a property can be written, enumerated, or reconfigured. Understanding property descriptors gives you fine-grained control over object behavior.

property-descriptors.js
JavaScript
1// Property descriptor attributes:
2// value — the property value
3// writable — can the value be changed?
4// enumerable — does it show up in for...in / Object.keys?
5// configurable — can the descriptor be changed or property deleted?
6
7// Getting a property descriptor
8const obj = { x: 42 };
9const desc = Object.getOwnPropertyDescriptor(obj, "x");
10console.log(desc);
11// { value: 42, writable: true, enumerable: true, configurable: true }
12
13// Default descriptor for Object.defineProperty
14const controlled = {};
15Object.defineProperty(controlled, "readonly", {
16 value: "cannot change me",
17 writable: false, // cannot reassign
18 enumerable: true, // shows up in enumeration
19 configurable: false, // cannot delete or redefine
20});
21
22controlled.readonly = "try to change"; // silently ignored (or TypeError in strict)
23console.log(controlled.readonly); // "cannot change me"
24delete controlled.readonly; // false (cannot delete)
25console.log("readonly" in controlled); // true — still there
26
27// Non-enumerable property
28const hidden = {};
29Object.defineProperty(hidden, "secret", {
30 value: "hidden value",
31 enumerable: false,
32});
33Object.defineProperty(hidden, "visible", {
34 value: "shown",
35 enumerable: true,
36});
37
38console.log(Object.keys(hidden)); // ["visible"]
39console.log(hidden.secret); // "hidden value"
40console.log("secret" in hidden); // true — exists but not enumerable
41
42// Property descriptors with getters/setters
43const temperature = {
44 _celsius: 0,
45 get fahrenheit() {
46 return this._celsius * 9 / 5 + 32;
47 },
48 set fahrenheit(value) {
49 this._celsius = (value - 32) * 5 / 9;
50 },
51};
52
53temperature.fahrenheit = 212;
54console.log(temperature._celsius); // 100
55console.log(temperature.fahrenheit); // 212
56
57// Define multiple properties at once
58Object.defineProperties(product, {
59 id: { value: 1, writable: false, configurable: false },
60 name: { value: "Widget", writable: true, enumerable: true },
61 price: {
62 get() { return this._price; },
63 set(v) {
64 if (v < 0) throw new Error("Price cannot be negative");
65 this._price = v;
66 },
67 enumerable: true,
68 },
69});
70
71// Prevent property addition
72const locked = {};
73Object.preventExtensions(locked);
74locked.a = 1; // fails silently (or TypeError in strict mode)
75console.log("a" in locked); // false
76
77// Check property descriptor flags
78console.log(Object.isExtensible(locked)); // false
79console.log(Object.isSealed(controlled)); // false
80console.log(Object.isFrozen(frozen)); // true
🔥

pro tip

Property descriptors are the foundation of JavaScript's reactive systems. Vue 2's reactivity used Object.defineProperty to intercept property access. Vue 3 and modern reactive libraries use Proxy instead, which is more powerful. However, Object.defineProperty remains essential for creating truly immutable or controlled object properties in library code and API design.
Prototypal Inheritance

JavaScript's prototypal inheritance is fundamentally different from classical inheritance. Objects inherit directly from other objects via the [[Prototype]] internal slot (accessed via Object.getPrototypeOf or the deprecated __proto__ accessor). When you access a property that doesn't exist on the object itself, JavaScript walks up the prototype chain.

prototypal-inheritance.js
JavaScript
1// Every object has a prototype
2const base = { type: "base" };
3const child = Object.create(base);
4child.name = "child";
5
6console.log(child.name); // "child" — own property
7console.log(child.type); // "base" — inherited from prototype
8console.log(child.toString); // function — inherited from Object.prototype
9
10// The prototype chain:
11// child → base → Object.prototype → null
12
13// Creating objects with Object.create
14const animal = {
15 init(name) {
16 this.name = name;
17 return this;
18 },
19 speak() {
20 console.log(`${this.name} makes a sound`);
21 },
22};
23
24const dog = Object.create(animal);
25dog.init("Rex");
26dog.speak(); // "Rex makes a sound"
27
28// Setting and getting prototypes
29const obj = {};
30console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
31
32const newProto = { customMethod() { return "from proto"; } };
33Object.setPrototypeOf(obj, newProto);
34console.log(obj.customMethod()); // "from proto"
35
36// Prototype chain traversal
37function getPrototypeChain(obj) {
38 const chain = [];
39 let current = obj;
40 while (current) {
41 chain.push(current.constructor?.name || "null prototype");
42 current = Object.getPrototypeOf(current);
43 }
44 return chain;
45}
46console.log(getPrototypeChain([]));
47// ["Array", "Object", null]
48
49// Shadowing — own property overrides prototype
50const proto = { value: 10 };
51const instance = Object.create(proto);
52console.log(instance.value); // 10 (from proto)
53
54instance.value = 20; // creates own property — shadows proto
55console.log(instance.value); // 20 (own)
56console.log(proto.value); // 10 (unchanged)
57delete instance.value;
58console.log(instance.value); // 10 (proto is now visible again)
59
60// The instanceof operator checks the prototype chain
61function Animal(name) {
62 this.name = name;
63}
64Animal.prototype.speak = function() {
65 console.log(`${this.name} says hello`);
66};
67
68const cat = new Animal("Whiskers");
69console.log(cat instanceof Animal); // true
70console.log(cat instanceof Object); // true
71console.log(cat.constructor === Animal); // true

best practice

While Object.setPrototypeOf exists, it is a very slow operation in every JavaScript engine — the engine cannot optimize objects whose prototype changes dynamically. For performance-critical code, set prototypes at creation time using Object.create or constructor functions. Never mutate __proto__ in production code.
Static Object Methods

The Object constructor provides a rich set of static methods for interacting with objects. These methods cover property enumeration, copying, sealing, freezing, and more. Modern JavaScript development relies heavily on these methods.

object-methods.js
JavaScript
1// Object.keys() — returns enumerable own property names
2const user = { name: "Alice", age: 30, role: "admin" };
3console.log(Object.keys(user)); // ["name", "age", "role"]
4
5// Object.values() — returns enumerable own property values
6console.log(Object.values(user)); // ["Alice", 30, "admin"]
7
8// Object.entries() — returns [key, value] pairs
9console.log(Object.entries(user));
10// [["name", "Alice"], ["age", 30], ["role", "admin"]]
11
12// Object.fromEntries() — create object from entries
13const entries = [["a", 1], ["b", 2], ["c", 3]];
14const obj = Object.fromEntries(entries);
15console.log(obj); // { a: 1, b: 2, c: 3 }
16
17// Practical: transform object properties
18const doubled = Object.fromEntries(
19 Object.entries(obj).map(([key, value]) => [key, value * 2])
20);
21console.log(doubled); // { a: 2, b: 4, c: 6 }
22
23// Practical: filter object properties
24const filtered = Object.fromEntries(
25 Object.entries(user).filter(([_, value]) => typeof value === "string")
26);
27console.log(filtered); // { name: "Alice", role: "admin" }
28
29// Object.assign() — copy properties (mutable)
30const target = { a: 1 };
31const source1 = { b: 2 };
32const source2 = { c: 3, a: 99 }; // a will overwrite target.a
33Object.assign(target, source1, source2);
34console.log(target); // { a: 99, b: 2, c: 3 }
35
36// Object.assign for cloning
37const clone = Object.assign({}, target);
38console.log(clone); // { a: 99, b: 2, c: 3 }
39
40// Object.is() — strict equality for special cases
41console.log(Object.is(NaN, NaN)); // true (=== would be false)
42console.log(Object.is(0, -0)); // false (=== would be true)
43console.log(Object.is(0, 0)); // true
44console.log(Object.is("hello", "hello")); // true
45
46// Object.hasOwn() — modern check for own property (ES2022)
47console.log(Object.hasOwn(user, "name")); // true
48console.log(Object.hasOwn(user, "toString")); // false (inherited)
49
50// Compare with hasOwnProperty
51console.log(user.hasOwnProperty("name")); // true
52// Object.create(null) objects don't have hasOwnProperty
53const nullProto = Object.create(null);
54nullProto.x = 1;
55// console.log(nullProto.hasOwnProperty("x")); // TypeError!
56console.log(Object.hasOwn(nullProto, "x")); // true — works safely
57
58// Object.getOwnPropertyNames() — all own properties (including non-enumerable)
59const withHidden = { visible: 1 };
60Object.defineProperty(withHidden, "hidden", {
61 value: 2,
62 enumerable: false,
63});
64console.log(Object.keys(withHidden)); // ["visible"]
65console.log(Object.getOwnPropertyNames(withHidden)); // ["visible", "hidden"]
66
67// Object.getOwnPropertySymbols() — symbol-keyed properties
68const sym = Symbol("secret");
69const hasSymbol = { [sym]: "hidden by symbol" };
70console.log(Object.getOwnPropertySymbols(hasSymbol)); // [Symbol(secret)]
71console.log(hasSymbol[sym]); // "hidden by symbol"
📝

note

Prefer Object.hasOwn() over hasOwnProperty() for two reasons: it works on objects created with Object.create(null), and it is slightly more concise. Additionally, Object.entries() combined with Object.fromEntries() enables powerful declarative object transformations that would otherwise require imperative loops.
The Prototype Chain in Depth

The prototype chain is JavaScript's mechanism for inheritance and shared behavior. When you access a property on an object, JavaScript first checks the object's own properties. If not found, it walks up the prototype chain until the property is found or the chain ends at null.

prototype-chain.js
JavaScript
1// Visualizing the prototype chain
2// Object.prototype is at the top of most prototype chains
3console.log(Object.prototype);
4// { constructor: Object, hasOwnProperty: fn, toString: fn, ... }
5
6// Function objects have a prototype property
7// (used when the function is called with new)
8function Foo() {}
9console.log(Foo.prototype); // { constructor: Foo }
10console.log(typeof Foo.prototype); // "object"
11
12// The prototype chain for instances:
13const foo = new Foo();
14// foo → Foo.prototype → Object.prototype → null
15console.log(Object.getPrototypeOf(foo) === Foo.prototype); // true
16console.log(Object.getPrototypeOf(Foo.prototype) === Object.prototype); // true
17console.log(Object.getPrototypeOf(Object.prototype)); // null — end of chain
18
19// Constructor functions and prototypes
20function Vehicle(type) {
21 this.type = type;
22 this.miles = 0;
23}
24
25Vehicle.prototype.drive = function(miles) {
26 this.miles += miles;
27 console.log(`Driving ${miles} miles`);
28};
29
30Vehicle.prototype.getInfo = function() {
31 return `${this.type} with ${this.miles} miles`;
32};
33
34const car = new Vehicle("Car");
35car.drive(100);
36console.log(car.getInfo()); // "Car with 100 miles"
37
38// Subclassing via prototype chain
39function Motorcycle(type) {
40 Vehicle.call(this, type); // call parent constructor
41 this.wheels = 2;
42}
43
44// Set up prototype chain: Motorcycle.prototype → Vehicle.prototype
45Motorcycle.prototype = Object.create(Vehicle.prototype);
46Motorcycle.prototype.constructor = Motorcycle;
47
48Motorcycle.prototype.wheelie = function() {
49 console.log("Popping a wheelie!");
50};
51
52const bike = new Motorcycle("Sport");
53bike.drive(50); // inherited from Vehicle
54bike.wheelie(); // own method
55console.log(bike.getInfo()); // "Sport with 50 miles"
56
57// instanceof walks the prototype chain
58console.log(bike instanceof Motorcycle); // true
59console.log(bike instanceof Vehicle); // true
60console.log(bike instanceof Object); // true
61console.log(bike instanceof Array); // false
62
63// Checking the prototype chain
64console.log(Vehicle.prototype.isPrototypeOf(bike)); // true
65console.log(Object.prototype.isPrototypeOf(bike)); // true
66
67// Property resolution order:
68// 1. Own properties
69// 2. Prototype properties
70// 3. Prototype's prototype properties
71// ... until null
72
73// Override methods by shadowing
74Motorcycle.prototype.drive = function(miles) {
75 console.log("Motorcycle driving differently");
76 Vehicle.prototype.drive.call(this, miles);
77};
78bike.drive(30); // "Motorcycle driving differently" then "Driving 30 miles"

warning

The instanceof operator checks the prototype chain at runtime. This means it can produce unexpected results if prototypes are manipulated after object creation. Also, instanceof fails across different JavaScript realms (e.g., iframes, different windows) because each realm has its own Object constructor. Use Array.isArray() instead of arr instanceof Array for this reason.
Object Creation Patterns

JavaScript offers multiple patterns for creating objects and defining reusable object structures. Each pattern has different trade-offs regarding memory, performance, encapsulation, and inheritance.

creation-patterns.js
JavaScript
1// Pattern 1: Object Literal — simple, one-off objects
2const point = { x: 10, y: 20 };
3
4// Pattern 2: Factory Function — returns a new object each call
5function createUser(name, age) {
6 return {
7 name,
8 age,
9 greet() {
10 console.log(`Hi, I'm ${this.name}`);
11 },
12 };
13}
14const user1 = createUser("Alice", 30);
15const user2 = createUser("Bob", 25);
16// Each user gets its own copy of greet() — more memory
17
18// Pattern 3: Factory with shared methods
19const userMethods = {
20 greet() { console.log(`Hi, I'm ${this.name}`); },
21 isAdult() { return this.age >= 18; },
22};
23
24function createUserEfficient(name, age) {
25 const user = Object.create(userMethods);
26 user.name = name;
27 user.age = age;
28 return user;
29}
30// Methods are shared via prototype — less memory
31
32// Pattern 4: Constructor Function
33function User(name, age) {
34 this.name = name;
35 this.age = age;
36}
37User.prototype.greet = function() {
38 console.log(`Hi, I'm ${this.name}`);
39};
40User.prototype.isAdult = function() {
41 return this.age >= 18;
42};
43const user3 = new User("Charlie", 28);
44
45// Pattern 5: Class Syntax (ES2015) — syntactic sugar over constructors
46class Person {
47 constructor(name, age) {
48 this.name = name;
49 this.age = age;
50 }
51
52 greet() {
53 console.log(`Hello, I'm ${this.name}`);
54 }
55
56 static createAnonymous() {
57 return new Person("Anonymous", 0);
58 }
59}
60const person1 = new Person("Diana", 32);
61const anonymous = Person.createAnonymous();
62
63// Pattern 6: Object.create with mixins
64const canEat = {
65 eat(food) { console.log(`Eating ${food}`); },
66};
67const canWalk = {
68 walk(steps) { console.log(`Walking ${steps} steps`); },
69};
70const canTalk = {
71 talk(words) { console.log(`Says: ${words}`); },
72};
73
74function createHuman(name) {
75 const human = Object.create(Object.assign({}, canEat, canWalk, canTalk));
76 human.name = name;
77 return human;
78}
79const alice = createHuman("Alice");
80alice.eat("pizza");
81alice.walk(100);
82alice.talk("Hello!");
83
84// Pattern 7: Object.assign for composition
85function createLogger(prefix) {
86 return {
87 log(msg) { console.log(`[${prefix}] ${msg}`); },
88 info(msg) { console.log(`[${prefix}] INFO: ${msg}`); },
89 error(msg) { console.log(`[${prefix}] ERROR: ${msg}`); },
90 };
91}
92
93function createStore() {
94 const logger = createLogger("Store");
95 return Object.assign({}, logger, {
96 state: {},
97 setState(key, value) {
98 this.state[key] = value;
99 this.log(`State updated: ${key} = ${JSON.stringify(value)}`);
100 },
101 });
102}

best practice

In modern JavaScript, class syntax is the preferred way to define object blueprints with inheritance. It is readable, familiar to developers from class-based languages, and works with the entire class ecosystem (extends, static, private fields, etc.). However, for simple objects without inheritance, factory functions or plain object literals are often cleaner. Choose the simplest pattern that meets your needs.
Private Properties & Encapsulation

JavaScript has evolved several approaches for creating private properties. From closures and conventions to true private fields (ES2022), each approach offers different trade-offs between privacy, performance, and debugging experience.

private-properties.js
JavaScript
1// Approach 1: Convention — underscore prefix (no real privacy)
2class Person {
3 constructor(name) {
4 this._name = name; // convention: private, but still accessible
5 }
6 getName() { return this._name; }
7}
8// const p = new Person("Alice");
9// p._name — still accessible, just a convention
10
11// Approach 2: Closure-based privacy
12function createCounter() {
13 let count = 0; // truly private via closure
14 return {
15 increment() { return ++count; },
16 decrement() { return --count; },
17 getCount() { return count; },
18 };
19}
20const counter = createCounter();
21counter.increment();
22counter.increment();
23console.log(counter.getCount()); // 2
24console.log(counter.count); // undefined — truly private
25
26// Approach 3: WeakMap-based privacy
27const _private = new WeakMap();
28
29class BankAccount {
30 constructor(owner, balance) {
31 _private.set(this, { owner, balance });
32 }
33
34 getBalance() {
35 return _private.get(this).balance;
36 }
37
38 deposit(amount) {
39 const data = _private.get(this);
40 data.balance += amount;
41 console.log(`Deposited ${amount}. Balance: ${data.balance}`);
42 }
43
44 withdraw(amount) {
45 const data = _private.get(this);
46 if (amount > data.balance) throw new Error("Insufficient funds");
47 data.balance -= amount;
48 return amount;
49 }
50}
51const account = new BankAccount("Alice", 1000);
52console.log(account.getBalance()); // 1000
53console.log(account.balance); // undefined — private!
54
55// Approach 4: Private class fields (ES2022) — best in modern code
56class Wallet {
57 #balance = 0; // private field
58 #owner; // private field
59
60 constructor(owner, initialBalance = 0) {
61 this.#owner = owner;
62 this.#balance = initialBalance;
63 }
64
65 #logTransaction(type, amount) { // private method
66 console.log(`${type}: ${amount}. Balance: ${this.#balance}`);
67 }
68
69 deposit(amount) {
70 this.#balance += amount;
71 this.#logTransaction("Deposit", amount);
72 }
73
74 withdraw(amount) {
75 if (amount > this.#balance) throw new Error("Insufficient funds");
76 this.#balance -= amount;
77 this.#logTransaction("Withdrawal", amount);
78 return amount;
79 }
80
81 get owner() { return this.#owner; }
82}
83
84const wallet = new Wallet("Bob", 500);
85wallet.deposit(200);
86// wallet.#balance — SyntaxError! Truly private at language level
87// wallet.#logTransaction — SyntaxError! Cannot access private method
88
89console.log(wallet.owner); // "Bob" — via getter
🔥

pro tip

Use private class fields (#field) for new code targeting ES2022+ environments. They provide true encapsulation enforced by the language — not just convention. Private fields are also more performant than WeakMap-based privacy. For older environments, the WeakMap pattern is the most secure closure-based approach. Never rely on underscore prefix for actual privacy.
Best Practices

Mastering JavaScript objects requires not just understanding the mechanics but also applying them wisely. These best practices will help you write robust, maintainable object-oriented JavaScript.

Use object spread ({...obj}) for immutable property updates — don't mutate objects directly
Prefer class syntax for object blueprints — it's cleaner and works with the broader class ecosystem
Use Object.hasOwn() instead of hasOwnProperty() — safer for null-prototype objects
Use Object.freeze() for configuration objects that should never change — catch bugs early
Leverage Object.entries() and Object.fromEntries() for declarative object transformations
Understand the difference between own and inherited properties — for...in includes both
Use private fields (#) for true encapsulation in modern code — not underscore conventions
Avoid deep prototype chains (3+ levels) — they make code harder to reason about and debug
Use Object.create(null) for dictionaries/maps — no prototype pollution from toString etc.
Prefer composition over inheritance — mixins and object.assign() often beat extends chains

best practice

The most important object lesson in JavaScript: objects are compared by reference, not by value. Two objects with identical properties are never equal ({a:1} === {a:1} is false). For deep equality checks, use a library like Lodash's isEqual or manually compare properties. This single concept causes more bugs than almost any other object feature.

Pattern Comparison Table

PatternMemoryEncapsulationInheritanceBest For
Object LiteralPer-instanceNonePrototype chainSimple data, configs
Factory FunctionPer-instanceClosureObject.createPrivate state, composition
ConstructorShared methodsNonePrototype chainClassic OOP patterns
ClassShared methods# fieldsextendsModern OOP, complex models
Object.createShared prototypeNoneDirect prototypalMixins, dynamic prototypes
$Blueprint — Engineering Documentation·Section ID: JS-OBJECTS·Revision: 1.0