7 Ways to Create Objects in JavaScript: Complete Guide to Object Creation Patterns
JavaScript offers seven distinct patterns for creating objects—from simple literals to ES6 classes and factory composition—each offering unique trade-offs in memory efficiency, encapsulation, and inheritance flexibility.
The leonardomso/33-js-concepts repository demonstrates every major object creation technique through executable test suites. Understanding the different ways to create objects in JavaScript allows you to select the optimal pattern for your specific use case, whether you need quick data structures or complex domain models with true privacy.
Object Literals for Quick Data Structures
The object literal syntax is the most concise way to create a plain object with Object.prototype as its prototype chain.
Use this approach for configuration objects, one-off data structures, or any scenario where you do not need shared behavior across multiple instances. In tests/object-oriented/object-creation-prototypes/object-creation-prototypes.test.js, the repository demonstrates this pattern for simple player definitions:
const player = { name: 'Alice', health: 100 };
console.log(player.name); // Alice
Object literals excel when you need immediate, readable object instantiation without the overhead of constructors or classes.
Object.create() for Explicit Prototype Chains
When you need an object that inherits from a specific prototype without invoking a constructor, use Object.create(proto, descriptors?).
This method creates a new object with the explicit prototype you supply, making it ideal for setting up prototype chains directly. The test file object-creation-prototypes.test.js shows this pattern when establishing inheritance relationships:
const proto = { attack() { return `${this.name} attacks!`; } };
const player = Object.create(proto);
player.name = 'Bob';
player.health = 100;
console.log(player.attack()); // Bob attacks!
Prefer Object.create() when you want fine-grained control over the prototype chain or when working with objects that should not follow the constructor instantiation pattern.
Factory Functions for Encapsulation and Privacy
Factory functions are regular functions that return new objects, closing over private variables to create true encapsulation.
This pattern avoids the new keyword entirely and allows you to hide implementation details via closure. According to tests/object-oriented/factories-classes/factories-classes.test.js, factories are the preferred pattern when you need private state without the complexity of class syntax:
function createPlayer(name) {
let health = 100; // private
return {
name,
attack() { return `${name} attacks!`; },
getHealth() { return health; }
};
}
const p = createPlayer('Carol');
console.log(p.attack()); // Carol attacks!
Factory functions shine in scenarios requiring data privacy or when you want to avoid the this binding complexities associated with constructors.
Constructor Functions with the new Keyword
Constructor functions capitalize on JavaScript’s new operator to create objects with a shared prototype.
When invoked with new, JavaScript creates an empty object, sets its [[Prototype]] to the function’s .prototype property, executes the constructor body with this bound to the new object, and returns the object. The factories-classes.test.js file demonstrates this legacy pattern:
function Player(name) {
this.name = name;
this.health = 100;
}
Player.prototype.attack = function () {
return `${this.name} attacks!`;
};
const p = new Player('Dave');
console.log(p.attack()); // Dave attacks!
Use constructor functions when working with legacy codebases or when you need prototype sharing without modern class syntax, though ES6 classes are generally preferred for new development.
ES6 Classes for Modern Object-Oriented Programming
ES6 classes provide syntactic sugar over constructor functions, offering a declarative syntax for constructors, prototype methods, static members, and inheritance.
Classes support extends, super, static fields, and private fields (#), making them ideal for modern codebases that favor clear object-oriented structures. As implemented in tests/object-oriented/factories-classes/factories-classes.test.js, classes provide the most self-documenting structure for complex domain models:
class Player {
#health = 100; // truly private
constructor(name) {
this.name = name;
}
attack() {
return `${this.name} attacks!`;
}
getHealth() {
return this.#health;
}
}
const p = new Player('Eve');
console.log(p.attack()); // Eve attacks!
ES6 classes are the standard choice for modern applications requiring hierarchical inheritance and strong typing conventions.
Class Fields and Arrow Functions for Auto-Binding
Class fields and arrow-function methods allow you to declare instance properties directly in the class body and ensure this binding for callbacks.
This pattern eliminates the need for manual binding in constructors, as arrow functions lexically capture this from the class instance. The repository demonstrates this in factories-classes.test.js under the section Arrow Function Class Fields:
class Button {
count = 0;
handleClick = () => {
this.count++;
return this.count;
};
}
const btn = new Button();
const click = btn.handleClick;
console.log(click()); // 1 – `this` stays bound
Use this approach when creating event handlers or callback methods where maintaining the correct this context is critical, or when you need per-instance values without explicitly defining a constructor.
Composition via Factories for Flexible Behavior
Composition (mixins) using factories allows you to combine multiple behaviors into a single object without deep inheritance hierarchies.
Small factory functions return behavior objects that are spread into a new object, enabling orthogonal, reusable functionality. This pattern is demonstrated in factories-classes.test.js under the Factory Composition section:
function canWalk(state) {
return {
walk() {
state.position += state.speed;
return `${state.name} walks to ${state.position}`;
}
};
}
function createDuck(name) {
const state = { name, position: 0, speed: 2 };
return {
name,
...canWalk(state)
};
}
const d = createDuck('Donald');
console.log(d.walk()); // Donald walks to 2
Prefer composition over inheritance when you need to mix multiple independent behaviors or avoid the rigidity of class hierarchies.
Architectural Considerations When Creating Objects
Selecting the right object creation pattern requires evaluating five key architectural concerns:
- Memory efficiency: Classes and constructor functions share prototype methods across all instances, referencing the same function object. Factory functions that recreate methods per instance consume additional memory.
- True privacy: ES2022 private fields (
#field) are truly hidden from external code. Factory closures also provide privacy, though with less explicit syntax than the hash prefix notation. instanceofsemantics: Only class-based and constructor-function objects participate ininstanceofchecks against their constructor. Plain objects from factories returninstanceof Objectonly.- Extensibility: Classes support clean hierarchical extension via
extendsandsuper. Factory composition works through object spreading, offering flexibility but no built-in prototype chain forinstanceofchecks. - Ease of use: Object literals offer maximum conciseness for static data, while class syntax provides the most readable structure for large domain models.
Summary
- Object literals provide the fastest syntax for one-off data structures and configuration objects.
Object.create()enables explicit prototype assignment without constructor invocation.- Factory functions deliver encapsulation via closures and avoid
newkeyword requirements. - Constructor functions offer legacy-compatible prototype sharing for existing codebases.
- ES6 classes supply modern, declarative syntax with support for static members, private fields, and inheritance.
- Class fields and arrow functions solve
thisbinding issues for callbacks and event handlers. - Composition via mixins creates flexible, orthogonal behaviors without deep inheritance chains.
Frequently Asked Questions
What is the most memory-efficient way to create objects in JavaScript?
Classes and constructor functions are the most memory-efficient patterns because they place methods on the shared prototype object. All instances reference the same function object rather than creating new function instances per object, as seen in the factories-classes.test.js test suite where prototype methods are defined once and shared across all Player instances.
How do I create truly private properties in JavaScript objects?
You have two robust options for true privacy. ES2022 private fields (syntax: #health) are hard-private and inaccessible outside the class body. Alternatively, factory functions close over variables in the function scope, making them accessible only to the returned object's methods. Both patterns are demonstrated in tests/object-oriented/factories-classes/factories-classes.test.js.
When should I use factory functions instead of ES6 classes?
Use factory functions when you need to avoid the new keyword, require true privacy through closures, or want to return different object shapes conditionally. Use ES6 classes when you need the instanceof operator to work, require inheritance via extends, or want the explicit structure of static methods and private fields that the class syntax provides.
Can I mix different object creation patterns in the same project?
Yes, modern JavaScript projects frequently combine patterns. You might use object literals for configuration constants, ES6 classes for domain models requiring inheritance, and factory composition for shared behaviors across unrelated classes. The 33-js-concepts repository itself mixes these patterns in object-creation-prototypes.test.js and factories-classes.test.js to demonstrate their interoperability.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →