How to Create Objects in JavaScript: Full Guide
Learn every way to create objects in JavaScript including object literals, constructors, Object.create, classes, factory functions, and dynamic property patterns with practical code examples.
JavaScript provides multiple ways to create objects, each suited to different situations. Whether you need a simple configuration object, a reusable blueprint for dozens of instances, or a prototype-based inheritance chain, picking the right creation pattern matters. Understanding all available options helps you write cleaner, more maintainable code and choose the pattern that fits your project's needs.
This guide walks you through every object creation method in JavaScript, from the basic object literal syntax to modern ES6 classes. You will see practical examples for each approach and learn when to use which pattern.
Object Literal Syntax
The object literal is the most common and simplest way to create objects in JavaScript. You wrap key-value pairs inside curly braces:
const user = {
firstName: "Alice",
lastName: "Johnson",
age: 28,
email: "alice@example.com",
isActive: true
};
console.log(user.firstName); // "Alice"
console.log(user.age); // 28When to Use Object Literals
Object literals work best for one-off objects where you do not need to create multiple instances with the same structure. Configuration objects, API response mappings, and function option parameters are ideal use cases:
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
debug: false
};
const searchParams = {
query: "javascript objects",
page: 1,
limit: 20,
sortBy: "relevance"
};Shorthand Property Names (ES6)
When the variable name matches the property name, you can use shorthand syntax:
const name = "Alice";
const age = 28;
const role = "developer";
// Without shorthand
const userOld = { name: name, age: age, role: role };
// With shorthand (ES6)
const userNew = { name, age, role };
console.log(userNew); // { name: "Alice", age: 28, role: "developer" }Computed Property Names
You can use expressions inside square brackets as property names:
const field = "email";
const prefix = "user";
const obj = {
[field]: "alice@example.com",
[`${prefix}Name`]: "Alice",
[`${prefix}Id`]: 42
};
console.log(obj.email); // "alice@example.com"
console.log(obj.userName); // "Alice"
console.log(obj.userId); // 42Constructor Functions
Constructor functions let you create multiple objects with the same structure. By convention, constructor names start with an uppercase letter. You call them with the new keyword:
function User(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.isActive = true;
}
User.prototype.getFullName = function() {
return `${this.firstName} ${this.lastName}`;
};
User.prototype.greet = function() {
return `Hi, I'm ${this.getFullName()}, age ${this.age}`;
};
const alice = new User("Alice", "Johnson", 28);
const bob = new User("Bob", "Smith", 32);
console.log(alice.getFullName()); // "Alice Johnson"
console.log(bob.greet()); // "Hi, I'm Bob Smith, age 32"
console.log(alice instanceof User); // trueWhat new Does Behind the Scenes
When you call new User(...), JavaScript performs four steps:
| Step | Action | Result |
|---|---|---|
| 1 | Creates a new empty object | {} |
| 2 | Links the object's prototype to User.prototype | Inherits methods |
| 3 | Calls User() with this bound to the new object | Properties assigned |
| 4 | Returns the new object (unless the function returns another object) | Instance ready |
Forgetting new
Calling a constructor without new causes this to point to the global object (or undefined in strict mode), creating bugs that are hard to track:
// WRONG: missing new
const broken = User("Charlie", "Brown", 25);
console.log(broken); // undefined (no return statement)
// In non-strict mode, firstName is now on the global object!
// Safe guard pattern
function SafeUser(name) {
if (!(this instanceof SafeUser)) {
return new SafeUser(name);
}
this.name = name;
}ES6 Classes
Classes are syntactic sugar over constructor functions and prototypes. They provide a cleaner, more familiar syntax for creating objects with shared methods:
class Product {
constructor(name, price, category) {
this.name = name;
this.price = price;
this.category = category;
this.inStock = true;
}
getFormattedPrice() {
return `$${this.price.toFixed(2)}`;
}
applyDiscount(percent) {
this.price = this.price * (1 - percent / 100);
return this;
}
toString() {
return `${this.name} (${this.getFormattedPrice()})`;
}
}
const laptop = new Product("MacBook Pro", 2499.99, "Electronics");
console.log(laptop.getFormattedPrice()); // "$2499.99"
laptop.applyDiscount(10);
console.log(laptop.toString()); // "MacBook Pro ($2249.99)"Class vs Constructor Function Comparison
| Feature | Constructor Function | ES6 Class |
|---|---|---|
| Syntax | function Name() {} | class Name {} |
| Methods | Added to .prototype manually | Defined inside class body |
| Hoisting | Function is hoisted | Class is NOT hoisted |
new required | No (fails silently) | Yes (throws error without new) |
typeof result | "function" | "function" |
| Inheritance | Object.create + manual wiring | extends keyword |
| Static methods | Name.method = ... | static method() {} |
Static Methods and Properties
Static members belong to the class itself, not to instances:
class MathHelper {
static PI = 3.14159265359;
static circleArea(radius) {
return MathHelper.PI * radius * radius;
}
static celsiusToFahrenheit(celsius) {
return (celsius * 9) / 5 + 32;
}
}
console.log(MathHelper.circleArea(5)); // 78.5398...
console.log(MathHelper.celsiusToFahrenheit(0)); // 32
// MathHelper.PI is accessible without creating an instanceObject.create()
Object.create() creates a new object with a specified prototype. This gives you direct control over the prototype chain without using constructors or classes:
const animal = {
type: "Unknown",
speak() {
return `The ${this.type} makes a sound`;
},
describe() {
return `I am a ${this.type}`;
}
};
const dog = Object.create(animal);
dog.type = "Dog";
dog.bark = function() {
return "Woof!";
};
console.log(dog.speak()); // "The Dog makes a sound"
console.log(dog.bark()); // "Woof!"
console.log(dog.describe()); // "I am a Dog"
// Prototype chain verification
console.log(Object.getPrototypeOf(dog) === animal); // trueObject.create with Property Descriptors
You can pass property descriptors as the second argument for fine-grained control:
const base = { role: "user" };
const admin = Object.create(base, {
name: {
value: "Admin",
writable: true,
enumerable: true,
configurable: true
},
level: {
value: 10,
writable: false,
enumerable: true,
configurable: false
}
});
console.log(admin.name); // "Admin"
console.log(admin.level); // 10
console.log(admin.role); // "user" (inherited from prototype)
admin.level = 20; // Silently fails (writable: false)
console.log(admin.level); // Still 10Factory Functions
Factory functions are regular functions that return new objects. They do not require new and offer more flexibility than constructors:
function createUser(firstName, lastName, role) {
const fullName = `${firstName} ${lastName}`;
return {
firstName,
lastName,
role,
getFullName() {
return fullName;
},
hasPermission(action) {
const permissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"]
};
return (permissions[role] || []).includes(action);
}
};
}
const admin = createUser("Alice", "Johnson", "admin");
const viewer = createUser("Bob", "Smith", "viewer");
console.log(admin.getFullName()); // "Alice Johnson"
console.log(admin.hasPermission("delete")); // true
console.log(viewer.hasPermission("write")); // falseFactory Functions vs Constructors
| Aspect | Factory Function | Constructor/Class |
|---|---|---|
| Calling syntax | createUser(...) | new User(...) |
this binding | No this issues | this can be lost |
instanceof check | Not possible | user instanceof User works |
| Private data | Closures (true privacy) | Conventions only (_name) |
| Memory | Each instance gets own methods | Methods shared via prototype |
| Flexibility | Can return any object type | Always returns same type |
Encapsulation with Closures
Factory functions achieve true private variables through closures because the returned object can only access private data through exposed methods:
function createBankAccount(owner, initialBalance) {
let balance = initialBalance; // Truly private
const transactions = []; // Truly private
return {
owner,
getBalance() {
return balance;
},
deposit(amount) {
if (amount <= 0) throw new Error("Deposit must be positive");
balance += amount;
transactions.push({ type: "deposit", amount, date: new Date() });
return balance;
},
withdraw(amount) {
if (amount <= 0) throw new Error("Withdrawal must be positive");
if (amount > balance) throw new Error("Insufficient funds");
balance -= amount;
transactions.push({ type: "withdrawal", amount, date: new Date() });
return balance;
},
getTransactionHistory() {
return [...transactions]; // Return copy, not reference
}
};
}
const account = createBankAccount("Alice", 1000);
account.deposit(500);
account.withdraw(200);
console.log(account.getBalance()); // 1300
console.log(account.balance); // undefined (truly private!)new Object() Constructor
The built-in Object constructor creates empty objects. This is equivalent to the literal {} but less common:
const obj1 = new Object();
obj1.name = "Alice";
obj1.age = 28;
// Equivalent to:
const obj2 = {};
obj2.name = "Alice";
obj2.age = 28;
// Object literal is preferred (shorter and clearer):
const obj3 = { name: "Alice", age: 28 };You will rarely see new Object() in modern JavaScript. The literal syntax is shorter, faster to parse, and universally preferred.
Object.assign() for Merging
Object.assign() copies properties from one or more source objects into a target object. This is useful for creating objects by combining smaller pieces:
const defaults = {
theme: "light",
language: "en",
notifications: true,
fontSize: 16
};
const userPreferences = {
theme: "dark",
fontSize: 18
};
const settings = Object.assign({}, defaults, userPreferences);
console.log(settings);
// { theme: "dark", language: "en", notifications: true, fontSize: 18 }The spread operator provides a more readable alternative for the same operation:
const settings = { ...defaults, ...userPreferences };Best Practices for Object Creation
- Use object literals for simple, one-off objects (config, options, data maps)
- Use classes when you need multiple instances with shared methods and inheritance
- Use factory functions when you need true private data or conditional object shapes
- Use Object.create when you need explicit prototype chain control
- Prefer shorthand properties (
{ name }over{ name: name }) for cleaner code - Freeze immutable objects with
Object.freeze()to prevent accidental modifications
const API_CONFIG = Object.freeze({
baseUrl: "https://api.example.com",
version: "v2",
timeout: 5000
});
API_CONFIG.timeout = 10000; // Silently fails (frozen)
console.log(API_CONFIG.timeout); // Still 5000Common Mistakes to Avoid
Modifying Shared Prototype Objects
// WRONG: mutating a shared prototype
const proto = { items: [] };
const a = Object.create(proto);
const b = Object.create(proto);
a.items.push("task 1");
console.log(b.items); // ["task 1"] - both share the SAME array!
// CORRECT: initialize own properties
const c = Object.create(proto);
c.items = []; // Own property shadows prototype
c.items.push("task 1");
console.log(proto.items); // [] - prototype unchangedUsing Object Literals Inside Loops
// Inefficient: creates new method copies each iteration
const users = [];
for (let i = 0; i < 1000; i++) {
users.push({
id: i,
getName() { return `User ${this.id}`; } // 1000 copies of this function
});
}
// Better: use a class or constructor (shared methods via prototype)
class User {
constructor(id) { this.id = id; }
getName() { return `User ${this.id}`; } // One copy shared by all instances
}
const betterUsers = [];
for (let i = 0; i < 1000; i++) {
betterUsers.push(new User(i));
}Real-World Example: Form Builder
Here is a practical example combining multiple creation patterns to build a form configuration system:
class FormField {
constructor(name, type, options = {}) {
this.name = name;
this.type = type;
this.required = options.required || false;
this.defaultValue = options.defaultValue || "";
this.validators = options.validators || [];
}
validate(value) {
if (this.required && !value) {
return { valid: false, error: `${this.name} is required` };
}
for (const validator of this.validators) {
const result = validator(value);
if (!result.valid) return result;
}
return { valid: true, error: null };
}
}
function createContactForm() {
const fields = [
new FormField("name", "text", { required: true }),
new FormField("email", "email", {
required: true,
validators: [
(val) => ({
valid: val.includes("@"),
error: "Invalid email format"
})
]
}),
new FormField("message", "textarea", {
required: true,
validators: [
(val) => ({
valid: val.length >= 10,
error: "Message must be at least 10 characters"
})
]
}),
new FormField("phone", "tel", { required: false })
];
return {
fields,
validateAll(data) {
const errors = {};
for (const field of fields) {
const result = field.validate(data[field.name]);
if (!result.valid) errors[field.name] = result.error;
}
return {
valid: Object.keys(errors).length === 0,
errors
};
}
};
}
const form = createContactForm();
const result = form.validateAll({
name: "Alice",
email: "invalid",
message: "Hi",
phone: ""
});
console.log(result);
// { valid: false, errors: { email: "Invalid email format", message: "Message must be at least 10 characters" } }Rune AI
Key Insights
- Object literals: Best for one-off objects like config, options, and data maps
- Constructor functions: The pre-ES6 way to create reusable object blueprints with shared prototype methods
- ES6 classes: Cleaner syntax over constructors with built-in
extends, static methods, and enforcednew - Object.create: Direct prototype chain control without constructors, useful for delegation patterns
- Factory functions: Return objects from regular functions, enabling true private data via closures
Frequently Asked Questions
Which object creation method should beginners learn first?
Are ES6 classes real classes like in Java or Python?
When should I use a factory function instead of a class?
Can I mix different object creation methods in one project?
What is the performance difference between object literals and classes?
Conclusion
JavaScript gives you multiple tools for creating objects, and each one serves a distinct purpose. Object literals handle simple data grouping, constructors and classes provide reusable blueprints with shared methods, Object.create gives you fine-grained prototype control, and factory functions deliver true encapsulation through closures. Choosing the right pattern depends on whether you need reusability, privacy, inheritance, or simplicity.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.