Objects are the central data structure in JavaScript. Almost everything is an object or can behave like one — arrays, functions, dates, regular expressions. Understanding how objects are created, how properties are looked up, and how inheritance works through the prototype chain is foundational knowledge for writing idiomatic JavaScript.
What is an object?
An object is a collection of key-value pairs called properties. Property keys are strings or Symbols; values can be any JavaScript value, including functions (in which case the property is usually called a method).
const car = {
make: "Toyota",
model: "Corolla",
year: 2022,
describe() {
return `${this.year} ${this.make} ${this.model}`;
}
};
console.log(car.make); // "Toyota"
console.log(car["model"]); // "Corolla"
console.log(car.describe()); // "2022 Toyota Corolla"
Three ways to create objects
Object literals
The most common approach. You write the properties directly in {} syntax.
const point = { x: 10, y: 20 };
Constructor functions
Before ES6 classes, constructor functions were the primary way to create objects that share behaviour. Call a constructor with new, and the engine creates a new object, sets its prototype, and runs the function with this bound to the new object.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function () {
return `Hi, I'm ${this.name}`;
};
const alice = new Person("Alice", 30);
console.log(alice.greet()); // "Hi, I'm Alice"
Object.create()
Object.create(proto) creates a new object whose prototype is set to proto. This lets you set up inheritance relationships explicitly, without using new or class syntax.
const animalProto = {
breathe() {
return `${this.name} breathes.`;
}
};
const dog = Object.create(animalProto);
dog.name = "Rex";
dog.bark = function () {
return "Woof!";
};
console.log(dog.breathe()); // "Rex breathes." — inherited from animalProto
console.log(dog.bark()); // "Woof!"
Pass null to create an object with no prototype at all — useful for pure dictionaries:
const dict = Object.create(null);
dict.key = "value";
// dict has no toString, hasOwnProperty, or any other inherited methods
The prototype chain
Every ordinary JavaScript object has an internal slot called [[Prototype]]. When you access a property on an object, the engine first looks at the object’s own properties. If the property is not found there, it follows the [[Prototype]] link and checks that object — and so on, up the chain — until it either finds the property or reaches null (the end of the chain).
const grandparent = { type: "living thing" };
const parent = Object.create(grandparent);
parent.kingdom = "animal";
const child = Object.create(parent);
child.name = "Rex";
console.log(child.name); // "Rex" — own property
console.log(child.kingdom); // "animal" — from parent
console.log(child.type); // "living thing" — from grandparent
console.log(child.size); // undefined — not found anywhere in chain
The prototype chain lookup only happens for reads. Writing a property always sets it directly on the object, even if a property with the same name exists higher up the chain. This is called property shadowing.
const proto = { x: 1 };
const obj = Object.create(proto);
console.log(obj.x); // 1 — read from proto
obj.x = 99; // creates an own property on obj, shadows proto.x
console.log(obj.x); // 99 — own property
console.log(proto.x); // 1 — proto unchanged
__proto__ vs prototype
These two terms cause significant confusion.
__proto__ is the accessor property (available on instances) that exposes the internal [[Prototype]] slot. It is part of the standard but considered a legacy API — prefer Object.getPrototypeOf() and Object.setPrototypeOf().
prototype is a regular property that exists on constructor functions (and class constructors). When you call new Foo(), the new object’s [[Prototype]] is set to Foo.prototype.
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function () {
return "Woof!";
};
const rex = new Dog("Rex");
// rex.__proto__ === Dog.prototype (instance's [[Prototype]])
// Dog.prototype.constructor === Dog
console.log(Object.getPrototypeOf(rex) === Dog.prototype); // true
console.log(rex.bark()); // "Woof!" — found on Dog.prototype
The relationship looks like this:
rex (instance)
└─ [[Prototype]] → Dog.prototype
└─ [[Prototype]] → Object.prototype
└─ [[Prototype]] → null
ES6 Classes
ES6 classes are syntactic sugar over the prototype-based pattern above. They do not introduce a new inheritance model — they compile down to the same constructor function and prototype assignment.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a noise.`;
}
}
class Dog extends Animal {
speak() {
return `${this.name} barks.`;
}
}
const d = new Dog("Rex");
console.log(d.speak()); // "Rex barks."
console.log(d instanceof Dog); // true
console.log(d instanceof Animal); // true
extends sets up the prototype chain: Dog.prototype’s [[Prototype]] is Animal.prototype. super calls the parent class’s constructor or methods.
hasOwnProperty and in
When iterating over an object or checking for a property, it matters whether you want own properties only or the entire prototype chain.
const parent = { inherited: true };
const obj = Object.create(parent);
obj.own = true;
// `in` checks own AND prototype chain
console.log("own" in obj); // true
console.log("inherited" in obj); // true
// hasOwnProperty checks own properties only
console.log(obj.hasOwnProperty("own")); // true
console.log(obj.hasOwnProperty("inherited")); // false
In modern code, prefer Object.hasOwn(obj, key) over obj.hasOwnProperty(key). It is a static method that avoids issues when working with objects that have no prototype (Object.create(null)) or where hasOwnProperty has been shadowed.
const obj = Object.create(null); // no prototype
obj.name = "Alice";
// obj.hasOwnProperty("name"); // TypeError — hasOwnProperty doesn't exist
Object.hasOwn(obj, "name"); // true — always safe
Property descriptors
Object properties have descriptors that control their behaviour:
value — the property’s value.
writable — whether the value can be changed.
enumerable — whether the property shows up in for...in loops and Object.keys().
configurable — whether the property can be deleted or its descriptor changed.
const obj = {};
Object.defineProperty(obj, "id", {
value: 42,
writable: false,
enumerable: false,
configurable: false
});
obj.id = 99; // silently fails (or throws in strict mode)
console.log(obj.id); // 42
console.log(Object.keys(obj)); // [] — "id" is not enumerable
Inspecting objects
const obj = { a: 1, b: 2 };
const proto = { c: 3 };
Object.setPrototypeOf(obj, proto);
Object.keys(obj); // ["a", "b"] — own enumerable keys
Object.values(obj); // [1, 2]
Object.entries(obj); // [["a", 1], ["b", 2]]
Object.getOwnPropertyNames(obj); // ["a", "b"] — own keys, including non-enumerable
for (const key in obj) {
console.log(key); // "a", "b", "c" — own + inherited enumerable
}
Summary
JavaScript’s object system is built entirely on prototype chains. Objects delegate property lookups to their prototype, which delegates to its prototype, and so on. Constructor functions and ES6 classes are two syntactic layers on top of the same underlying mechanism. Once you understand that new Foo() creates an object whose [[Prototype]] points to Foo.prototype, the entire inheritance model — including extends, super, and instanceof — follows naturally.