Frontend Development 16 min read

Front‑End Design Patterns in JavaScript: Creational, Structural, and Behavioral

This article introduces front‑end design patterns, explains the three main categories—creational, structural, and behavioral—lists ten common JavaScript patterns, and provides clear explanations and runnable code examples for each pattern to improve code readability, maintainability, and scalability.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Front‑End Design Patterns in JavaScript: Creational, Structural, and Behavioral

Through recent interviews the author noticed many front‑end developers with several years of experience are unfamiliar with basic design patterns, prompting this comprehensive guide on what design patterns are, how they are classified, and why they matter for JavaScript development.

In front‑end development, a design pattern is a reusable solution to a recurring problem; applying patterns can increase code readability, maintainability, and extensibility. The patterns are divided into three high‑level types: creational , structural , and behavioral .

The article focuses on JavaScript and presents ten commonly used patterns, grouped by the three categories.

Creational Patterns

1. Singleton

Idea: Ensure a class has only one instance and provide a global access point.

Benefit: Global uniqueness and controlled shared resources.

Example (ES6 module):

const test = {
  name: 'testName',
  age: '18',
};
export default test;

import test from './test';
console.log(test.name, test.age); // prints: testName 18

2. Factory

Idea: Encapsulate object creation behind a common interface.

Benefit: High encapsulation, low coupling, easy extension.

Example:

// Define a product class
class TestProduct {
  constructor(productName) {
    this.productName = productName;
  }
  getName() {
    console.log(`产品名称: ${this.productName}`);
  }
}
// Factory function
function createProduct(name) {
  return new TestProduct(name);
}
const test1 = createProduct('产品1');
const test2 = createProduct('产品2');
test1.getName(); // 产品名称: 产品1
test2.getName(); // 产品名称: 产品2

3. Constructor (Builder‑like) Pattern

Idea: Use a constructor to create objects with varying parameters.

Benefit: Reduces duplicate code, improves maintainability.

Example:

class TestPerson {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  introduce() {
    console.log(`姓名: ${this.name}, 年龄: ${this.age}`);
  }
}
const p1 = new TestPerson('张三', 30);
const p2 = new TestPerson('李四', 25);
p1.introduce(); // 姓名: 张三, 年龄: 30
p2.introduce(); // 姓名: 李四, 年龄: 25

Structural Patterns

1. Adapter

Idea: Convert one interface to another so that incompatible classes can work together.

Benefit: Increases reuse, reduces coupling, adds flexibility.

Example:

class Receptacle {
  plugIn() { console.log('110V 插座'); }
}
class ForeignReceptacle {
  plugIn220V() { console.log('220V 插座'); }
}
class VoltageAdapter {
  constructor(foreignReceptacle) { this.foreignReceptacle = foreignReceptacle; }
  plugIn() { this.foreignReceptacle.plugIn220V(); }
}
const receptacle = new Receptacle();
receptacle.plugIn(); // 110V 插座
const foreign = new ForeignReceptacle();
const adapter = new VoltageAdapter(foreign);
adapter.plugIn(); // 220V 插座

2. Decorator

Idea: Wrap an object to add new behavior without modifying the original.

Benefit: Dynamic extension, code reuse.

Example:

function getGreet(name) { console.log(`你好啊,${name}!`); }
function welcomePrefix(greetFn) {
  return function(name) {
    console.log('欢迎啊');
    greetFn(name);
  };
}
getGreet('天天鸭'); // 你好啊,天天鸭!
const setWelcome = welcomePrefix(getGreet);
setWelcome('天天'); // 欢迎啊  你好啊,天天!

3. Proxy

Idea: Provide a surrogate object that controls access to the original.

Benefit: Intercept operations, improve reuse, add cross‑cutting concerns.

Example:

function counterEvent() {
  let count = 0;
  return {
    setCount: () => { count++; },
    getCount: () => count,
  };
}
function countProxy() {
  const c = counterEvent();
  return {
    setCount: () => c.setCount(),
    getCount: () => c.getCount(),
  };
}
const myCounter = countProxy();
myCounter.setCount();
myCounter.setCount();
myCounter.setCount();
console.log(myCounter.getCount()); // 3

Behavioral Patterns

1. Observer

Idea: Subjects notify observers about state changes (one‑to‑many).

Benefit: Loose coupling, dynamic addition/removal of observers.

Example:

class Sub {
  constructor() { this.observers = []; }
  add(observer) { this.observers.push(observer); }
  unadd(observer) { this.observers = this.observers.filter(o => o !== observer); }
  notify(msg) { this.observers.forEach(o => o(msg)); }
}
const createObs = name => msg => console.log(`${name} 收到: ${msg}`);
const sub = new Sub();
const o1 = createObs('观察者1');
const o2 = createObs('观察者2');
sub.add(o1); sub.add(o2);
sub.notify('你好鸭!'); // both receive
sub.unadd(o1);
sub.notify('再见!'); // only o2 receives

2. Publish‑Subscribe

Idea: Many‑to‑many relationship where publishers emit events and subscribers listen.

Benefit: Complete decoupling, flexible event handling.

Example:

class Pub {
  constructor() { this.subobj = {}; }
  subscribe(event, cb) { (this.subobj[event] = this.subobj[event] || []).push(cb); }
  unsubscribe(event, cb) { if (this.subobj[event]) this.subobj[event] = this.subobj[event].filter(fn => fn !== cb); }
  publish(event, data) { if (this.subobj[event]) this.subobj[event].forEach(fn => fn(data)); }
}
const pub = new Pub();
const handler1 = msg => console.log(`订阅者1 收到: ${msg}`);
const handler2 = msg => console.log(`订阅者2 收到: ${msg}`);
pub.subscribe('greet', handler1);
pub.subscribe('greet', handler2);
pub.publish('greet', '你好鸭!'); // both receive
pub.unsubscribe('greet', handler1);
pub.publish('greet', '再见!'); // only handler2 receives

3. Command

Idea: Encapsulate a request as an object, allowing parameterization and queuing.

Benefit: Decouples invoker from receiver, enables undo/redo, flexible execution.

Example:

class TestLight { on() { console.log('打开灯了'); } off() { console.log('关闭灯了'); } }
class Comm { constructor(receiver) { this.receiver = receiver; } }
class LightOnComm extends Comm { execute() { this.receiver.on(); } }
class LightOffComm extends Comm { execute() { this.receiver.off(); } }
class RemoteControl { onButton(comm) { comm.execute(); } }
const light = new TestLight();
const onCmd = new LightOnComm(light);
const offCmd = new LightOffComm(light);
const remote = new RemoteControl();
remote.onButton(onCmd); // 打开灯了
remote.onButton(offCmd); // 关闭灯了

4. Template Method

Idea: Define the skeleton of an algorithm in a base class, allowing subclasses to override specific steps.

Benefit: Centralized control flow, easy to extend or modify individual steps.

Example:

class Game {
  initGame() { console.log('初始化'); }
  startGame() { console.log('游戏开始'); }
  onGame() { console.log('游戏中'); }
  endGame() { console.log('游戏结束'); }
  run() { this.initGame(); this.startGame(); this.onGame(); this.endGame(); }
}
const g = new Game();
g.run();

Understanding and applying these patterns helps developers read open‑source code more effectively and design robust, maintainable front‑end applications.

design patternsfrontendsoftware architectureJavaScriptbehavioralcreationalstructural
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.