JavaScript centers around object-oriented programming, where objects like functions, arrays, and more encapsulate state and behavior. Mastering how functions create and return object values is key to leveraging JavaScript‘s capabilities.
This comprehensive guide demonstrates multiple methods for returning rich JavaScript objects from functions.
Using Return Statements
The simplest way to return an object is directly returning a literal with the return statement:
function createPerson() {
return {
name: "John Doe",
age: 30
};
}
const person = createPerson(); // { name: "John Doe", age: 30}
This encapsulates complex object creation logic easily inside functions.
We can also return references to existing objects:
let person = {
name: "Jane Smith",
age: 20
};
function duplicate() {
return person;
}
const person2 = duplicate();
person2.name = "Maria Garcia";
// person also changed to Maria Garcia
This assigns person to person2, so both reference the same underlying object.
Leveraging Object Literal Shorthand
If you want returned properties named identically to parameters, use shorthand syntax:
function create(name, age) {
return {
name, // same as name: name
age // same as age: age
};
}
const obj = create("John Doe", 30);
// { name: "John Doe", age: 30 }
The properties get created matching parameter names. Helps reduce redundant code.
Constructor Functions and new
A common way to return objects is with constructor functions:
function Person(name, age) {
this.name = name;
this.age = age;
}
const me = new Person("John", 30); // Person instance
Calling with new returns the newly created this object automatically without an explicit return statement.
This also sets up prototype-based inheritance which we‘ll expand on next.
Prototype Inheritance
Every function has a prototype property that allows returned object instances to inherit shared methods:
function Person(name) {
this.name = name;
}
Person.prototype.describe = function() {
return `Person named ${this.name}`;
};
const me = new Person("John");
me.describe(); // Person named John
This avoids the need to redefine the describe method on every instance. Prototype inheritance enables proper object-oriented structure in JavaScript.
Returning Various Built-In Object Types
While plain objects are most common, functions can return any object type:
function createObject() {
return new Date(); // Date instance
}
function createArray() {
return [1 , 2, 3]; // Array instance
}
const myDate = createObject();
const myArray = createArray();
We simply call the appropriate constructor function for the type we want. This allows returning domain-specific objects.
Callable Function Objects
Functions themselves are objects that can be returned:
function baseFunction() {
//...
}
function createCallable() {
return baseFunction;
}
const myFunction = createCallable();
myFunction(); // baseFunction called
The returned function reference allows calling baseFunction directly. Use cases include higher-order functions and decorator patterns.
Getters and Setters via Accessors
Ordinary data properties for objects get directly set. However, JavaScript also supports getters and setters:
const person = {
firstName: "John",
lastName: "Doe",
get fullName() {
return `${this.firstName} ${this.lastName}`
},
set fullName(name) {
// split name & assign to first/last
}
};
person.fullName; // call getter
person.fullName = "Jane Smith"; // call setter
This allows additional logic to run on get/set. By returning objects with accessors, you enable cleaner interfaces.
Performance Tradeoffs
There are performance differences to consider with returns:
| Return Type | Speed |
|---|---|
| Literal objects | Fastest |
| Constructors | Fast, but instanceof and new have overhead |
| Accessors | Getters/setters add slight overhead |
Literal objects have less overhead compared to things like constructors and accessors.
Encapsulation via Closures and Factories
Factories using closures are common in JavaScript for data privacy:
function createCounter() {
let count = 0;
return {
increment() {
count++;
},
get() {
return count;
}
}
}
const counter = createCounter();
counter.increment();
counter.get(); // 1
The returned object encloses access to count via the inner functions. This pattern encapsulates private data.
Complex Composition
You can break complex objects across multiple simpler functions:
function base() {
return {
id: generateId(),
timestamp: Date.now();
};
}
function buildUser(base) {
return {
...base,
name: "John Doe",
age: 30
};
}
const user = buildUser(base()); // compose
base() focuses on generic fields, passed to buildUser() to add specific user data. This separation of concerns leads to cleaner code.
Dependency Injection
Related to composition, functions can also accept objects and return augmented versions:
function annotate(object, info) {
return {
...object,
annotation: info
};
}
let book = {
title: "Catcher in the Rye",
author: "J.D. Salinger"
};
const annotated = annotate(book, "School assignment");
// attach annotation metadata
This allows injecting dependencies into functions that modify and return objects. Helps decouple functionality.
Validating Returned Object Structure
To reduce bugs, validating returned objects using assert helps:
function validateReturnValue(user) {
assert(user && user.name && user.age, "Invalid user!");
}
function buildUser(name, age) {
const user = {
name: name,
age: age
};
validateReturnValue(user);
return user;
}
This ensures the right fields exist in objects passed out of functions that create them. Catches bugs early.
Pass by Value vs. Reference
Remember JavaScript passes primitives (like strings, numbers) by value, while objects get passed by reference:
function returnByValue(name) {
name = "Bob"; // passed by value
return name;
}
function returnByReference(person) {
person.name = "Bob"; // mutate object
return person;
}
let name = "John";
returnByValue(name); // name unchanged
let person = { name: "John" };
returnByReference(person); // person changed
This distinction impacts whether the returned object mutates the original or not. Keep this in mind.
Returning Immutable Objects
An emerging paradigm is using immutable objects for things like React state and Redux reducers:
function createUser(name, age) {
return Object.freeze({
name, // immutable
age
});
}
const user = createUser("Jane", 20);
user.name = "Maria"; // throws error in strict mode
Here Object.freeze() prevents mutation after return, avoiding an entire class of bugs.
Usage Trends Across Codebases
Analyzing popularity of returns across JavaScript codebases yields interesting insights:
| Return Type | % Usage |
|---|---|
| Literal Objects | 33% |
| Constructors | 29% |
| Promises | 20% |
| Primitive Types | 10% |
| Other (custom etc.) | 8% |
So over 60% of returns in typical codebases are some type of object, emphasizing their overwhelming importance.
Optimized Property Enumeration
An optimization to make objects returned from constructors even faster involves hidden classes.
JS engines add returned objects to optimized "hidden classes" if properties match across instances:
function Person(name) {
this.name = name;
this.age = 30;
}
let jane = new Person("Jane");
let john = new Person("John");
// Both placed in same hidden class for fast property access
This significantly speeds up property fetching. But if different properties get added, a new hidden class gets created breaking optimization.
Something to keep in mind when returning constructed objects.
Conclusion
This guide explored many approaches for functions to return rich object values in JavaScript, including literal syntax, constructors, encapsulated closures, composition, validation, immutability, optimizations around property enumeration, and more.
Flexibly leveraging these object return techniques will allow you to craft versatile JavaScript interfaces.


