#Ways to create objects
A JavaScript object is two things: a set of own properties (key → value pairs stored on the object itself) and one hidden link, [[Prototype]], to another object it falls back to when a key is missing. Every way of creating an object is really answering two questions: what own properties does it start with, and what does its prototype link point to?
That's the mental model to carry through this whole chapter. An object literal links to Object.prototype. Object.create(x) links to whatever x you pass. new Fn() links to Fn.prototype. A class does the same as new Fn() with nicer syntax. A factory function just returns a literal. Once you see the six forms below as "different ways of setting that one link", they stop feeling like six unrelated features.
// 1. Object literal — the everyday way
const person = { firstName: "John", lastName: "Doe", age: 50 };
// 2. Object constructor — same result, more typing
const p2 = new Object();
p2.firstName = "John";
// 3. Object.create — choose the prototype explicitly
const personProto = {
greet() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age}.`);
},
};
const p3 = Object.create(personProto);
p3.name = "John";
p3.age = 30;
p3.greet(); // Hello, my name is John and I'm 30.
// 4. Constructor function + new
function Person(name) {
this.name = name;
}
const p4 = new Person("Alice");
// 5. ES2015 class (sugar over 4)
class PersonClass {
constructor(name) {
this.name = name;
}
}
// 6. Factory function — no `new`, no `this`
const createPerson = (name) => ({ name, greet: () => `Hi ${name}` });- Literal:
persongets three own properties (firstName,lastName,age) and its[[Prototype]]is set toObject.prototypeautomatically. That link is whyperson.toString()works even though you never wrote atoString. new Object()builds exactly the same thing as{}— an empty object linked toObject.prototype— and thenp2.firstName = "John"adds one own property. There's no advantage over the literal, which is why nobody writes it.Object.create(personProto)makes an empty object whose[[Prototype]]ispersonProto. The two assignments givep3own propertiesname: "John"andage: 30. When you callp3.greet(), JavaScript doesn't findgreetonp3, follows the link topersonProto, finds it there, and calls it withthis = p3(the object left of the dot) — so it printsJohnand30.new Person("Alice")creates an empty object linked toPerson.prototype, runsPersonwiththispointing at that object (sothis.name = "Alice"becomes an own property), and returns it.p4is{ name: "Alice" }withPerson.prototypebehind it — see Whatnewactually does.class PersonClassproduces the same shape as step 4: a constructor function plus aPersonClass.prototypeobject. The differences are guard rails — callingPersonClass()withoutnewthrowsTypeError: Class constructor PersonClass cannot be invoked without 'new', and the class body always runs in strict mode.- The factory is a plain function that returns a new literal, so the result is linked to
Object.prototype.greetis an arrow that closes over the parameternameinstead of readingthis.name, so it keeps working even when detached:const { greet } = createPerson("Ann"); greet()returns"Hi Ann". The cost is that every call builds a newgreetfunction (see Methods go on.prototype).
Which one to reach for:
- Literal for data, config and one-off objects — almost all objects in real code.
Object.create(proto)when you want to pick the prototype yourself, most oftenObject.create(null)for a null-prototype dictionary.class(or a constructor function in older code) when you'll create many objects that share behaviour. Methods live once on the prototype and every instance uses them.- Factory when you want privacy through closures and no
thispitfalls, and you're not creating so many objects that the per-object functions matter.