Factory functions provide a lightweight pattern for object creation in JavaScript. Unlike classes, factory functions offer flexibility through customizable construction, private data encapsulation, and dynamic prototypes. With UI complexity increasing in modern applications, understanding factory functions is key for scalable and modular JavaScript code.
What Makes Factories Powerful?
Some key strengths of the factory pattern:
Simplicity
Factories avoid class boilerplate and new calls by using normal functions and syntax:
function createUser(name) {
return {
name: name
};
}
Encapsulation
Closures give factories private data not exposed in returned objects:
function createCounter() {
let count = 0;
return {
getCount() {
return count;
}
}
}
Dynamic Prototypes
Object.create() allows factories to inherit from any arbitrary object:
const proto = {
logThis() {
console.log(this);
}
};
function createUser(name) {
let user = Object.create(proto);
user.name = name;
return user;
}
Containment
Factories allow grouping objects without needing class hierarchies.
Efficiency
Factory instances require fewer resources than equivalent class constructs. [1]
These capabilities make factories ideal for creating compartmentalized and disposable objects.
Performance and Memory Characteristics
Factories can provide better performance in some cases due to avoiding class abstractions. Some JSPerf benchmarks show:
- 2-4x less memory usage for factories creating objects with single properties [2]
- Faster instantiation through normal functions instead of
newcalls [3]
However, accessor heavy objects perform 2-8x slower with factories due to identifier resolution differences. [1]
So factories have tradeoffs depending on object access patterns. They work best for creating many disposable objects.
Common Real-World Use Cases
Some areas where factory functions shine:
UI Components
For UI elements like popups, notifications and widgets, factories provide flexibility:
function createPopup(html, duration) {
return {
render(rootElem) {
// mount popup DOM to rootElem
},
destroy() {
// teardown popup
}
};
}
const modal = createPopup(‘<div/>‘, 1000);
Creates reusable popups without needing classes.
Game Objects
Dynamic attribute assignment allows factories to model unique game characters/assets:
function createCharacter(name, powers) {
let char = {
name: name
};
// assign special powers
powers.forEach(p => char[p] = function() {
// power ability
});
return char;
}
const mage = createCharacter(‘Merlin‘, [‘fireball‘, ‘teleport‘]);
Easy to expand capabilities.
Math Utilities
Factories provide namespacing without boxy hierarchies:
const vec2 = {
create(x, y) {
let vec = Object.create(vec2);
vec.x = x;
vec.y = y;
return vec;
},
magnitude(vec) {
return Math.sqrt(vec.x*vec.x + vec.y*vec.y);
}
};
const point = vec2.create(3, 4);
vec2.magnitude(point); // 5
Keeps related functions together.
These examples demonstrate factory flexibility for domain needs without rigid taxonomy.
Industry Trends
More developers are leveraging factories as web apps grow in complexity:
- React uses factories for creating component instances [4]
- Vue employs factories to extend component options [5]
- Angular utilizes factories for services and controllers [6]
Many libraries also use factories to enable customization like Redux [7].
These trends align with increased UI dynamism requiring object versatility that factories provide.
Managing Object Relations
A downside of factories is organizing domain objects without hierarchies. Some patterns that help:
Factory Constructor Hybrid
Use factories to encapsulate constructor logic:
function Person(name) {
this.name = name;
}
function createPerson(name, age) {
let person = new Person(name);
person.age = age;
return person;
}
Keeps Person constructor reusable while exposingfactory API.
Factory Roles
Define factory roles for consistent construction:
const factory = {
base(traits) {
// common object setup
},
admin(traits) {
// admin extensions
}
};
factory.base({name: ‘Sarah‘});
factory.admin({name: ‘Jane‘});
Provides base object abstraction.
Extendable Factories
Support plugins/extensions:
function createGame(settings) {
let game = {
// base game setup
};
function installPlugin(plugin) {
// attach plugin modules
}
return Object.assign(game, { installPlugin });
}
const game = createGame({...});
game.installPlugin(graphicsModule);
Allows expanding capabilities.
These patterns demonstrate approaches to organize domain logic across factories.
Architectural Tradeoffs
Classes provide more formal structure with inheritance, whereas factories offer flexibility. This makes factories suitable for:
- Encapsulating implementation details
- Expanding objects dynamically
- Ad hoc compositions of behavior
And classes preferable for:
- Rigid taxonomies of objects/roles
- Base class abstractions
- Polymorphic hierarchies
Consider balancing these factors when weighing factories versus classes:
| Factories | Classes | |
|---|---|---|
| Declarative relations | Weaker | Stronger |
| Structure rigidity | Lower | Higher |
| Suitable complexity | Low-Medium | High |
| Object customization | Higher | Lower |
Functional Factories
Factories lend themselves well to functional style code due to lacking this.
We can build reusable factory modules with closures:
function createCounterCreator() {
let index = 0;
return {
createCounter() {
return {
getCount() {
return index++;
}
}
}
}
}
// usage
const counters = createCounterCreator();
const c1 = counters.createCounter();
const c2 = counters.createCounter();
c1.getCount(); // 0
c2.getCount(); // 1
The factory closure retains state across instances.
We can further enforce immutability by returning copies:
function clone(obj) {
return JSON.parse(JSON.stringify(obj))
}
function createVector(x, y) {
return clone({ x, y });
}
const a = createVector(10, 20);
const b = createVector(10, 20);
a === b; // false
Now vectors cannot be mutated in place.
These examples demonstrate factory synergy with functional programming principles.
Beyond JavaScript
Other languages have their own factory implementations:
Python
Returns class instances from factory methods:
def create_person(name):
return Person(name)
C#
Utilizes dedicated Factory class:
public class PersonFactory {
public static Person CreatePerson(string name) {
return new Person {
Name = name
};
}
}
TypeScript
Factory functions but with types:
interface Person {
name: string
}
function createPerson(name: string): Person {
return {
name
}
}
createPerson(123); // errors
These examples showcase idiomatic factories in other popular languages.
Architecting Factory Logic
When building significant logic with factories, consider separating concerns:
/**
* Factory abstraction
*/
function createUserFactory() {
let id = 0;
return {
createUser(name) {
// reuse encapsulated logic
}
}
}
/**
* Application code
*/
const userFactory = createUserFactory();
const user = userFactory.createUser(‘Bob‘);
This moves factory state management away from application code.
We can further abstract logic into plugins:
functionreportingPlugin(userFactory) {
function createUser(name) {
let user = userFactory.createUser(name);
// custom logic...
return user;
}
return { createUser };
}
const plugin = reportingPlugin(createUserFactory());
Keeps factory concerns modular.
These patterns demonstrate scaling factories by separating responsibilities.
Conclusion
Factory functions enable flexible and customizable construction of objects in JavaScript. Key strengths like encapsulation, dynamic prototypes, and simplicity make them suitable for:
- Domains requiring high variance
- Disposable objects
- Custom compositions
Factory class blends and functional factories also showcase the versatility of the pattern.
Understanding how to leverage factories along with rigid hierarchies of classes allows managing code complexity. Factories provide an indispensable dynamic tool for crafting scalable and modular systems.


