前端常用模式:提升代码质量的四大核心模式
引言
在前端开发中,随着应用复杂度不断增加,代码的组织和管理变得越来越重要。本文将深入探讨前端开发中四种极其有用的模式:中间件模式、管道模式、链式调用和惰性加载。这些模式不仅能提高代码的可读性和可维护性,还能显著优化应用性能。
一、中间件模式(Middleware Pattern)
中间件模式允许我们在请求和响应的处理流程中插入多个处理阶段, 这种模式在Node.js框架(例如Koa、Express)中广泛应用, 但在前端同样有其用武之地。
1.1 核心概念
中间件本质上是一个函数, 它可以:
- 访问请求(request)和响应(response)对象
- 执行任何代码
- 修改请求和响应对象
- 结束请求-响应周期
- 调用下一个中间件
1.2 基础实现
// 简单中间件系统实现
class MiddlewareSystem {
constructor() {
this.middlewares = [];
this.context = {};
}
// 添加中间件
use(middleware) {
if (typeof middleware !== 'function') {
throw new Error('Middleware must be a function');
}
this.middlewares.push(middleware);
return this; // 支持链式调用
}
// 执行中间件
async run(input) {
this.context = { ...input };
// 创建next函数
let index = 0;
const next = async () => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
await middleware(this.context, next);
}
};
try {
await next();
return this.context;
} catch (error) {
console.error('Middleware execution error:', error);
throw error;
}
}
}
// 使用示例
const system = new MiddlewareSystem();
// 添加中间件
system
.use(async (ctx, next) => {
console.log('Middleware 1: Start');
ctx.timestamp = Date.now();
await next();
console.log('Middleware 1: End');
})
.use(async (ctx, next) => {
console.log('Middleware 2: Start');
ctx.user = { id: 1, name: 'John' };
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 100));
await next();
console.log('Middleware 2: End');
})
.use(async (ctx, next) => {
console.log('Middleware 3: Start');
ctx.data = { message: 'Hello World' };
// 不再调用next(),结束执行
console.log('Middleware 3: End (no next call)');
});
// 运行中间件
system.run({ requestId: '123' }).then(result => {
console.log('Final context:', result);
});
1.3 错误处理中间件
class ErrorHandlingMiddleware {
constructor() {
this.middlewares = [];
this.errorMiddlewares = [];
}
use(middleware) {
this.middlewares.push(middleware);
return this;
}
useError(errorMiddleware) {
this.errorMiddlewares.push(errorMiddleware);
return this;
}
async run(input) {
const context = { ...input };
let index = 0;
let errorIndex = 0;
let error = null;
const next = async (err) => {
if (err) {
error = err;
errorIndex = 0;
return await nextError();
}
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
try {
await middleware(context, next);
} catch (err) {
await next(err);
}
}
};
const nextError = async () => {
if (errorIndex < this.errorMiddlewares.length) {
const errorMiddleware = this.errorMiddlewares[errorIndex++];
try {
await errorMiddleware(error, context, nextError);
} catch (err) {
error = err;
await nextError();
}
}
};
await next();
return { context, error };
}
}
// 使用示例
const errorSystem = new ErrorHandlingMiddleware();
errorSystem
.use(async (ctx, next) => {
console.log('Processing...');
// 模拟错误
if (!ctx.user) {
throw new Error('User not found');
}
await next();
})
.useError(async (err, ctx, next) => {
console.error('Error caught:', err.message);
ctx.error = err.message;
ctx.status = 'error';
await next();
});
errorSystem.run({}).then(result => {
console.log('Result with error handling:', result);
});
1.4 前端应用场景
// 前端请求拦截中间件
class RequestInterceptor {
constructor() {
this.interceptors = [];
this.defaultConfig = {
timeout: 5000,
headers: {}
};
}
use(interceptor) {
this.interceptors.push(interceptor);
return this;
}
async request(url, config = {}) {
const context = {
url,
config: { ...this.defaultConfig, ...config },
response: null,
error: null
};
let index = 0;
const next = async () => {
if (index < this.interceptors.length) {
const interceptor = this.interceptors[index++];
await interceptor(context, next);
} else {
// 执行实际请求
await this.executeRequest(context);
}
};
await next();
return context;
}
async executeRequest(context) {
try {
const response = await fetch(context.url, context.config);
context.response = await response.json();
} catch (error) {
context.error = error;
}
}
}
// 创建请求拦截器
const api = new RequestInterceptor();
api
.use(async (ctx, next) => {
console.log('Auth interceptor');
ctx.config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`;
await next();
})
.use(async (ctx, next) => {
console.log('Logging interceptor');
console.log(`Request to ${ctx.url}`, ctx.config);
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(`Request completed in ${duration}ms`);
})
.use(async (ctx, next) => {
console.log('Cache interceptor');
const cacheKey = `cache_${ctx.url}`;
const cached = localStorage.getItem(cacheKey);
if (cached && !ctx.config.noCache) {
ctx.response = JSON.parse(cached);
console.log('Using cached response');
} else {
await next();
if (ctx.response && !ctx.error) {
localStorage.setItem(cacheKey, JSON.stringify(ctx.response));
}
}
});
// 使用拦截器
api.request('https://api.example.com/data', { method: 'GET' })
.then(result => console.log('Response:', result.response));
二、管道模式(Pipeline Pattern)
管道模式将多个处理函数连接起来, 数据像流水一样经过这些函数进行处理和转换。
2.1 基本实现
// 同步管道
const pipeline = (...fns) => (initialValue) => {
return fns.reduce((value, fn) => {
if (typeof fn !== 'function') {
throw new Error(`Pipeline expects functions, got ${typeof fn}`);
}
return fn(value);
}, initialValue);
};
// 异步管道
const asyncPipeline = (...fns) => async (initialValue) => {
let result = initialValue;
for (const fn of fns) {
if (typeof fn !== 'function') {
throw new Error(`Pipeline expects functions, got ${typeof fn}`);
}
result = await fn(result);
}
return result;
};
// 可中断的管道
const breakablePipeline = (...fns) => (initialValue) => {
let shouldBreak = false;
let breakValue = null;
const breakFn = (value) => {
shouldBreak = true;
breakValue = value;
};
const result = fns.reduce((value, fn) => {
if (shouldBreak) return breakValue;
return fn(value, breakFn);
}, initialValue);
return shouldBreak ? breakValue : result;
};
2.2 数据处理管道示例
// 数据清洗管道
const dataCleaningPipeline = pipeline(
// 1. 移除空值
(data) => data.filter(item => item != null),
// 2. 标准化字段
(data) => data.map(item => ({
...item,
name: item.name?.trim().toLowerCase() || 'unknown',
value: Number(item.value) || 0
})),
// 3. 去重
(data) => {
const seen = new Set();
return data.filter(item => {
const key = `${item.name}-${item.value}`;
if (seen.has(key)) return false;
seen.add(key);
return true;
});
},
// 4. 排序
(data) => data.sort((a, b) => b.value - a.value),
// 5. 限制数量
(data) => data.slice(0, 10)
);
// 使用管道
const rawData = [
{ name: ' John ', value: '100' },
{ name: 'Jane', value: 200 },
null,
{ name: 'John', value: '100' }, // 重复
{ name: '', value: 'invalid' }
];
const cleanedData = dataCleaningPipeline(rawData);
console.log('Cleaned data:', cleanedData);
2.3 表单验证管道
// 验证规则
const validators = {
required: (value) => ({
isValid: value != null && value.toString().trim() !== '',
message: 'This field is required'
}),
minLength: (min) => (value) => ({
isValid: value?.toString().length >= min,
message: `Minimum length is ${min} characters`
}),
maxLength: (max) => (value) => ({
isValid: value?.toString().length <= max,
message: `Maximum length is ${max} characters`
}),
email: (value) => ({
isValid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
message: 'Invalid email format'
}),
numeric: (value) => ({
isValid: !isNaN(parseFloat(value)) && isFinite(value),
message: 'Must be a number'
})
};
// 表单验证管道
class FormValidator {
constructor(rules) {
this.rules = rules;
}
validate(data) {
const errors = {};
const results = {};
for (const [field, fieldRules] of Object.entries(this.rules)) {
const value = data[field];
const fieldErrors = [];
for (const rule of fieldRules) {
const result = rule(value);
if (!result.isValid) {
fieldErrors.push(result.message);
}
}
if (fieldErrors.length > 0) {
errors[field] = fieldErrors;
} else {
results[field] = value;
}
}
return {
isValid: Object.keys(errors).length === 0,
errors,
results
};
}
}
// 使用示例
const registrationRules = {
username: [
validators.required,
validators.minLength(3),
validators.maxLength(20)
],
email: [
validators.required,
validators.email
],
age: [
validators.required,
validators.numeric,
(value) => ({
isValid: value >= 18 && value <= 100,
message: 'Age must be between 18 and 100'
})
]
};
const validator = new FormValidator(registrationRules);
const userData = {
username: 'johndoe',
email: 'john@example.com',
age: 25
};
const validationResult = validator.validate(userData);
console.log('Validation result:', validationResult);
三、链式调用(Chaining Pattern)
链式调用通过返回对象实例本身(this), 允许连续调用多个方法, 使代码更加流畅易读。
3.1 基础链式调用
// jQuery风格的链式调用
class QueryBuilder {
constructor() {
this.query = {
select: [],
from: null,
where: [],
orderBy: [],
limit: null,
offset: null
};
}
select(...fields) {
this.query.select.push(...fields);
return this;
}
from(table) {
this.query.from = table;
return this;
}
where(condition) {
this.query.where.push(condition);
return this;
}
orderBy(field, direction = 'ASC') {
this.query.orderBy.push({ field, direction });
return this;
}
limit(count) {
this.query.limit = count;
return this;
}
offset(count) {
this.query.offset = count;
return this;
}
build() {
const { select, from, where, orderBy, limit, offset } = this.query;
if (!from) {
throw new Error('FROM clause is required');
}
let sql = `SELECT ${select.length > 0 ? select.join(', ') : '*'} FROM ${from}`;
if (where.length > 0) {
sql += ` WHERE ${where.join(' AND ')}`;
}
if (orderBy.length > 0) {
const orderClauses = orderBy.map(({ field, direction }) => `${field} ${direction}`);
sql += ` ORDER BY ${orderClauses.join(', ')}`;
}
if (limit !== null) {
sql += ` LIMIT ${limit}`;
}
if (offset !== null) {
sql += ` OFFSET ${offset}`;
}
return sql + ';';
}
reset() {
this.query = {
select: [],
from: null,
where: [],
orderBy: [],
limit: null,
offset: null
};
return this;
}
}
// 使用示例
const sql = new QueryBuilder()
.select('id', 'name', 'email')
.from('users')
.where('active = true')
.where('age >= 18')
.orderBy('name', 'ASC')
.limit(10)
.offset(0)
.build();
console.log('Generated SQL:', sql);
3.2 DOM操作链式调用
class DOMElement {
constructor(selector) {
if (typeof selector === 'string') {
this.elements = Array.from(document.querySelectorAll(selector));
} else if (selector instanceof Element) {
this.elements = [selector];
} else if (Array.isArray(selector)) {
this.elements = selector;
} else {
this.elements = [];
}
}
// CSS相关方法
css(property, value) {
if (typeof property === 'object') {
this.elements.forEach(el => {
Object.assign(el.style, property);
});
} else if (value !== undefined) {
this.elements.forEach(el => {
el.style[property] = value;
});
}
return this;
}
addClass(className) {
this.elements.forEach(el => {
el.classList.add(className);
});
return this;
}
removeClass(className) {
this.elements.forEach(el => {
el.classList.remove(className);
});
return this;
}
toggleClass(className) {
this.elements.forEach(el => {
el.classList.toggle(className);
});
return this;
}
// 内容操作
text(content) {
if (content !== undefined) {
this.elements.forEach(el => {
el.textContent = content;
});
return this;
}
return this.elements[0]?.textContent || '';
}
html(content) {
if (content !== undefined) {
this.elements.forEach(el => {
el.innerHTML = content;
});
return this;
}
return this.elements[0]?.innerHTML || '';
}
// 属性操作
attr(name, value) {
if (value !== undefined) {
this.elements.forEach(el => {
el.setAttribute(name, value);
});
return this;
}
return this.elements[0]?.getAttribute(name) || null;
}
// 事件处理
on(event, handler, options = {}) {
this.elements.forEach(el => {
el.addEventListener(event, handler, options);
});
return this;
}
off(event, handler, options = {}) {
this.elements.forEach(el => {
el.removeEventListener(event, handler, options);
});
return this;
}
// 遍历
each(callback) {
this.elements.forEach((el, index) => {
callback.call(el, index, el);
});
return this;
}
// 查找子元素
find(selector) {
const found = [];
this.elements.forEach(el => {
found.push(...Array.from(el.querySelectorAll(selector)));
});
return new DOMElement(found);
}
// 获取父元素
parent() {
const parents = this.elements.map(el => el.parentElement);
return new DOMElement(parents.filter(Boolean));
}
// 显示/隐藏
show() {
return this.css('display', '');
}
hide() {
return this.css('display', 'none');
}
// 动画
animate(properties, duration = 300, easing = 'ease') {
this.elements.forEach(el => {
el.style.transition = `all ${duration}ms ${easing}`;
Object.assign(el.style, properties);
setTimeout(() => {
el.style.transition = '';
}, duration);
});
return this;
}
}
// 使用示例
// 假设HTML中有: <div id="myDiv">Hello</div>
const $ = (selector) => new DOMElement(selector);
$('#myDiv')
.css({
color: 'white',
backgroundColor: 'blue',
padding: '10px'
})
.addClass('highlight')
.text('Hello, World!')
.on('click', function() {
$(this).toggleClass('active');
})
.animate({
opacity: 0.8,
transform: 'scale(1.1)'
}, 300);
3.3 构建器模式与链式调用
// 配置对象构建器
class ConfigurationBuilder {
constructor() {
this.config = {
api: {},
ui: {},
features: {},
performance: {}
};
}
// API配置
withApi(baseUrl) {
this.config.api.baseUrl = baseUrl;
return this;
}
withApiVersion(version) {
this.config.api.version = version;
return this;
}
withTimeout(ms) {
this.config.api.timeout = ms;
return this;
}
// UI配置
withTheme(theme) {
this.config.ui.theme = theme;
return this;
}
withLanguage(lang) {
this.config.ui.language = lang;
return this;
}
withDarkMode(enabled) {
this.config.ui.darkMode = enabled;
return this;
}
// 功能配置
enableFeature(feature) {
this.config.features[feature] = true;
return this;
}
disableFeature(feature) {
this.config.features[feature] = false;
return this;
}
// 性能配置
withCache(enabled) {
this.config.performance.cache = enabled;
return this;
}
withLazyLoad(enabled) {
this.config.performance.lazyLoad = enabled;
return this;
}
withCompression(enabled) {
this.config.performance.compression = enabled;
return this;
}
// 构建方法
build() {
// 验证配置
this.validate();
// 返回不可变配置
return Object.freeze(JSON.parse(JSON.stringify(this.config)));
}
validate() {
const { api } = this.config;
if (!api.baseUrl) {
throw new Error('API base URL is required');
}
if (api.timeout && api.timeout < 100) {
throw new Error('Timeout must be at least 100ms');
}
}
}
// 使用示例
const config = new ConfigurationBuilder()
.withApi('https://api.example.com')
.withApiVersion('v1')
.withTimeout(5000)
.withTheme('dark')
.withLanguage('en')
.withDarkMode(true)
.enableFeature('analytics')
.enableFeature('notifications')
.disableFeature('debug')
.withCache(true)
.withLazyLoad(true)
.build();
console.log('App configuration:', config);
四、惰性加载/求值(Lazy Loading/Evaluation)
惰性加载延迟计算或初始化, 直到真正需要时才执行, 可以显著提高应用性能。
4.1 惰性求值实现
// 惰性函数
function lazy(fn) {
let result;
let evaluated = false;
return function(...args) {
if (!evaluated) {
result = fn.apply(this, args);
evaluated = true;
}
return result;
};
}
// 惰性属性
function lazyProperty(target, propertyName, getter) {
let value;
let evaluated = false;
Object.defineProperty(target, propertyName, {
get() {
if (!evaluated) {
value = getter.call(this);
evaluated = true;
}
return value;
},
enumerable: true,
configurable: true
});
}
// 惰性类属性
class LazyClass {
constructor() {
this._expensiveData = null;
}
get expensiveData() {
if (this._expensiveData === null) {
console.log('Computing expensive data...');
// 模拟耗时计算
this._expensiveData = this.computeExpensiveData();
}
return this._expensiveData;
}
computeExpensiveData() {
// 复杂的计算逻辑
const start = Date.now();
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i);
}
console.log(`Computed in ${Date.now() - start}ms`);
return result;
}
// 重置惰性值
reset() {
this._expensiveData = null;
}
}
4.2 图片懒加载
class LazyImageLoader {
constructor(options = {}) {
this.options = {
root: null,
rootMargin: '0px',
threshold: 0.1,
placeholder: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
...options
};
this.images = new Map();
this.observer = null;
this.initObserver();
}
initObserver() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver(
this.handleIntersection.bind(this),
this.options
);
}
}
registerImage(imgElement, src) {
if (!imgElement || !src) return;
// 保存原始src
const originalSrc = imgElement.getAttribute('data-src') || src;
imgElement.setAttribute('data-src', originalSrc);
// 设置占位符
imgElement.src = this.options.placeholder;
imgElement.classList.add('lazy-load');
// 添加到观察列表
this.images.set(imgElement, {
src: originalSrc,
loaded: false
});
if (this.observer) {
this.observer.observe(imgElement);
} else {
// 降级方案:立即加载
this.loadImage(imgElement);
}
}
handleIntersection(entries) {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
this.loadImage(img);
this.observer?.unobserve(img);
}
});
}
async loadImage(imgElement) {
const imageData = this.images.get(imgElement);
if (!imageData || imageData.loaded) return;
try {
// 预加载图片
await this.preloadImage(imageData.src);
// 应用实际图片
imgElement.src = imageData.src;
imgElement.classList.remove('lazy-load');
imgElement.classList.add('lazy-loaded');
imageData.loaded = true;
// 触发加载完成事件
imgElement.dispatchEvent(new CustomEvent('lazyload', {
detail: { src: imageData.src }
}));
} catch (error) {
console.error('Failed to load image:', imageData.src, error);
imgElement.classList.add('lazy-error');
imgElement.dispatchEvent(new CustomEvent('lazyloaderror', {
detail: { src: imageData.src, error }
}));
}
}
preloadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
// 批量注册图片
registerAll(selector = 'img[data-src]') {
const images = document.querySelectorAll(selector);
images.forEach(img => {
const src = img.getAttribute('data-src');
if (src) {
this.registerImage(img, src);
}
});
}
// 强制加载特定图片
forceLoad(imgElement) {
this.loadImage(imgElement);
}
// 清理
destroy() {
this.observer?.disconnect();
this.images.clear();
}
}
// 使用示例
document.addEventListener('DOMContentLoaded', () => {
const lazyLoader = new LazyImageLoader({
threshold: 0.5,
placeholder: '/images/placeholder.png'
});
// 注册现有图片
lazyLoader.registerAll();
// 动态添加的图片
document.addEventListener('newImagesAdded', (event) => {
const newImages = event.detail.images;
newImages.forEach(img => {
lazyLoader.registerImage(img, img.dataset.src);
});
});
// 页面离开时清理
window.addEventListener('beforeunload', () => {
lazyLoader.destroy();
});
});
4.3 组件懒加载(Vue/React示例)
// React组件懒加载
import React, { Suspense, lazy } from 'react';
// 懒加载组件
const LazyComponent = lazy(() => import('./ExpensiveComponent'));
// 使用Suspense包裹
function App() {
return (
<div>
<h1>My App</h1>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
// Vue组件懒加载
const LazyComponent = () => import('./ExpensiveComponent.vue');
// 路由配置中使用懒加载
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
},
{
path: '/settings',
component: () => import('./views/Settings.vue')
}
];
// 自定义懒加载封装
function createLazyLoader(importFn, loadingComponent, errorComponent) {
return {
data() {
return {
component: null,
error: null,
loading: true
};
},
async created() {
try {
const module = await importFn();
this.component = module.default || module;
} catch (err) {
this.error = err;
console.error('Failed to load component:', err);
} finally {
this.loading = false;
}
},
render(h) {
if (this.loading && loadingComponent) {
return h(loadingComponent);
}
if (this.error && errorComponent) {
return h(errorComponent, { error: this.error });
}
if (this.component) {
return h(this.component);
}
return null;
}
};
}
4.4 数据懒加载(无限滚动)
class InfiniteScroll {
constructor(options = {}) {
this.options = {
container: document.documentElement,
distance: 100,
throttle: 200,
onLoadMore: () => Promise.resolve(),
...options
};
this.loading = false;
this.hasMore = true;
this.throttleTimer = null;
this.init();
}
init() {
this.container = typeof this.options.container === 'string'
? document.querySelector(this.options.container)
: this.options.container;
if (!this.container) {
console.error('Container not found');
return;
}
this.bindEvents();
}
bindEvents() {
this.container.addEventListener('scroll', this.handleScroll.bind(this));
window.addEventListener('resize', this.handleScroll.bind(this));
}
handleScroll() {
if (this.throttleTimer) {
clearTimeout(this.throttleTimer);
}
this.throttleTimer = setTimeout(() => {
this.checkPosition();
}, this.options.throttle);
}
checkPosition() {
if (this.loading || !this.hasMore) return;
const scrollTop = this.container.scrollTop;
const scrollHeight = this.container.scrollHeight;
const clientHeight = this.container.clientHeight;
const distanceToBottom = scrollHeight - (scrollTop + clientHeight);
if (distanceToBottom <= this.options.distance) {
this.loadMore();
}
}
async loadMore() {
if (this.loading || !this.hasMore) return;
this.loading = true;
this.container.dispatchEvent(new CustomEvent('loadstart'));
try {
const result = await this.options.onLoadMore();
if (result && typeof result.hasMore === 'boolean') {
this.hasMore = result.hasMore;
}
this.container.dispatchEvent(new CustomEvent('load', {
detail: result
}));
} catch (error) {
this.container.dispatchEvent(new CustomEvent('error', {
detail: error
}));
console.error('Failed to load more data:', error);
} finally {
this.loading = false;
this.container.dispatchEvent(new CustomEvent('loadend'));
// 检查是否还需要继续加载(数据可能没有填满屏幕)
if (this.hasMore) {
setTimeout(() => this.checkPosition(), 100);
}
}
}
// 手动触发加载
triggerLoad() {
this.loadMore();
}
// 重置状态
reset() {
this.loading = false;
this.hasMore = true;
}
// 销毁
destroy() {
this.container.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleScroll);
if (this.throttleTimer) {
clearTimeout(this.throttleTimer);
}
}
}
// 使用示例
const infiniteScroll = new InfiniteScroll({
container: '#scrollContainer',
distance: 200,
throttle: 300,
async onLoadMore() {
// 模拟API调用
const response = await fetch(`/api/items?page=${currentPage}`);
const data = await response.json();
// 渲染新数据
renderItems(data.items);
// 返回是否有更多数据
return { hasMore: data.hasMore };
}
});
// 动态更新选项
function updateInfiniteScroll(options) {
infiniteScroll.options = { ...infiniteScroll.options, ...options };
}
五、模式对比与应用场景
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 中间件模式 | 解耦、可组合、易于测试 | 可能增加复杂性、调试困难 | 请求处理、数据处理管道、插件系统 |
| 管道模式 | 清晰的数据流向、易于测试和复用 | 可能创建太多小函数、错误处理复杂 | 数据转换、验证、清洗流程 |
| 链式调用 | 代码流畅、易读、减少临时变量 | 可能掩盖错误来源、调试困难 | 构建器模式、DOM操作、配置设置 |
| 惰性加载 | 提高性能】减少内存使用 | 初始化延迟、复杂性增加 | 图片加载、组件加载、数据计算 |
5.1 如何选择模式?
- 需要处理请求/响应流程 → 中间件模式
- 需要数据转换流水线 → 管道模式
- 需要流畅的API接口 → 链式调用
- 需要优化性能,延迟初始化 → 惰性加载
六、综合应用实例
6.1 完整的API客户端
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
this.middlewares = [];
this.defaultHeaders = {
'Content-Type': 'application/json'
};
}
// 添加中间件
use(middleware) {
this.middlewares.push(middleware);
return this;
}
// 创建请求管道
async request(endpoint, options = {}) {
const context = {
url: `${this.baseURL}${endpoint}`,
options: {
method: 'GET',
headers: { ...this.defaultHeaders, ...options.headers },
...options
},
response: null,
error: null,
data: null
};
// 执行中间件管道
await this.executeMiddleware(context);
if (context.error) {
throw context.error;
}
return context.response;
}
async executeMiddleware(context) {
let index = 0;
const middlewares = this.middlewares;
const next = async () => {
if (index < middlewares.length) {
const middleware = middlewares[index++];
await middleware(context, next);
} else {
// 执行实际请求
await this.executeRequest(context);
}
};
await next();
}
async executeRequest(context) {
try {
const response = await fetch(context.url, context.options);
context.response = {
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
data: await response.json()
};
} catch (error) {
context.error = error;
}
}
// 快捷方法(链式调用)
get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}
post(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
}
// 创建API客户端并配置中间件
const api = new ApiClient('https://api.example.com')
.use(async (ctx, next) => {
// 认证中间件
const token = localStorage.getItem('auth_token');
if (token) {
ctx.options.headers.Authorization = `Bearer ${token}`;
}
await next();
})
.use(async (ctx, next) => {
// 日志中间件
console.log(`[API] ${ctx.options.method} ${ctx.url}`);
const start = Date.now();
await next();
const duration = Date.now() - start;
console.log(`[API] Completed in ${duration}ms`);
})
.use(async (ctx, next) => {
// 错误处理中间件
try {
await next();
} catch (error) {
console.error('[API] Request failed:', error);
// 可以在这里实现重试逻辑
throw error;
}
});
// 使用示例
async function fetchUserData() {
try {
const response = await api.get('/users/1');
console.log('User data:', response.data);
return response.data;
} catch (error) {
console.error('Failed to fetch user:', error);
}
}
6.2 表单处理系统
class FormProcessor {
constructor(formElement) {
this.form = formElement;
this.fields = new Map();
this.validators = new Map();
this.transformers = [];
this.submitHandlers = [];
this.init();
}
init() {
// 收集表单字段
Array.from(this.form.elements).forEach(element => {
if (element.name) {
this.fields.set(element.name, element);
}
});
// 绑定提交事件
this.form.addEventListener('submit', this.handleSubmit.bind(this));
}
// 添加验证器
addValidator(fieldName, validator) {
if (!this.validators.has(fieldName)) {
this.validators.set(fieldName, []);
}
this.validators.get(fieldName).push(validator);
return this;
}
// 添加数据转换器
addTransformer(transformer) {
this.transformers.push(transformer);
return this;
}
// 添加提交处理器
onSubmit(handler) {
this.submitHandlers.push(handler);
return this;
}
// 获取表单数据
getData() {
const data = {};
this.fields.forEach((element, name) => {
data[name] = this.getValue(element);
});
return data;
}
getValue(element) {
if (element.type === 'checkbox') {
return element.checked;
} else if (element.type === 'radio') {
return element.checked ? element.value : null;
} else if (element.type === 'select-multiple') {
return Array.from(element.selectedOptions).map(opt => opt.value);
}
return element.value;
}
// 验证表单
validate() {
const errors = {};
const data = this.getData();
this.validators.forEach((validators, fieldName) => {
const value = data[fieldName];
const fieldErrors = [];
validators.forEach(validator => {
const result = validator(value, data);
if (result !== true) {
fieldErrors.push(result || `Validation failed for ${fieldName}`);
}
});
if (fieldErrors.length > 0) {
errors[fieldName] = fieldErrors;
this.showFieldError(fieldName, fieldErrors[0]);
} else {
this.clearFieldError(fieldName);
}
});
return {
isValid: Object.keys(errors).length === 0,
errors,
data
};
}
showFieldError(fieldName, message) {
const field = this.fields.get(fieldName);
if (field) {
field.classList.add('error');
let errorElement = field.parentElement.querySelector('.error-message');
if (!errorElement) {
errorElement = document.createElement('div');
errorElement.className = 'error-message';
field.parentElement.appendChild(errorElement);
}
errorElement.textContent = message;
}
}
clearFieldError(fieldName) {
const field = this.fields.get(fieldName);
if (field) {
field.classList.remove('error');
const errorElement = field.parentElement.querySelector('.error-message');
if (errorElement) {
errorElement.remove();
}
}
}
// 处理提交
async handleSubmit(event) {
event.preventDefault();
// 验证
const validation = this.validate();
if (!validation.isValid) {
console.log('Form validation failed:', validation.errors);
return;
}
// 数据转换管道
let processedData = validation.data;
for (const transformer of this.transformers) {
processedData = transformer(processedData);
}
// 执行提交处理器
for (const handler of this.submitHandlers) {
try {
const result = await handler(processedData);
if (result === false || result?.stopPropagation) {
break;
}
} catch (error) {
console.error('Submit handler error:', error);
break;
}
}
}
// 重置表单
reset() {
this.form.reset();
this.fields.forEach((field, name) => {
this.clearFieldError(name);
});
return this;
}
}
// 使用示例
const formProcessor = new FormProcessor(document.getElementById('myForm'))
.addValidator('email', (value) => {
if (!value) return 'Email is required';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return 'Invalid email format';
}
return true;
})
.addValidator('password', (value) => {
if (!value) return 'Password is required';
if (value.length < 8) return 'Password must be at least 8 characters';
return true;
})
.addTransformer((data) => {
// 转换数据
return {
...data,
email: data.email.toLowerCase().trim(),
createdAt: new Date().toISOString()
};
})
.onSubmit(async (data) => {
console.log('Submitting data:', data);
// 模拟API调用
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('Registration failed');
}
alert('Registration successful!');
return true;
})
.onSubmit((data) => {
// 第二个处理器:发送分析事件
console.log('Analytics event sent for registration');
});
七、最佳实践与注意事项
7.1 中间件模式最佳实践
- 保持中间件简洁单一: 每个中间件只做一件事
- 错误处理: 确保中间件有适当的错误处理机制
- 性能考虑: 避免在中间件中进行昂贵的同步操作
- 顺序很重要: 注意中间件的执行顺序对业务逻辑的影响
7.2 管道模式最佳实践
- 纯函数优先: 确保管道中的函数是纯函数,避免副作用
- 类型检查: 考虑添加运行时类型检查
- 错误处理: 现管道级别的错误处理机制
- 性能优化: 考虑使用流式处理大数据集
7.3 链式调用最佳实践
- 返回this: 确保每个链式方法都返回实例本身
- 不可变操作: 考虑实现不可变版本的链式调用
- 清晰的方法名: 方法名应该清晰表达其功能
- 文档完善: 链式调用可能隐藏复杂度,需要良好文档
7.4 惰性加载最佳实践
- 适度使用: 不要过度使用惰性加载,会增加复杂度
- 预加载策略: 对于可能很快需要的内容,考虑预加载
- 错误处理: 确保惰性加载失败时有降级方案
- 用户反馈: 加载过程中给用户适当的反馈
总结
这四种前端模式-中间件模式、管道模式、链式调用和惰性加载---都是现代前端开发中极其有用的工具。它们各自解决了不同的问题:
- 中间件模式提供了处理复杂流程的模块化方式
- 管道模式让数据转换变得清晰和可组合
- 链式调用创造了流畅、易读的API
- 惰性加载优化了性能和资源使用
掌握这些模式并知道何时使用它们,将帮助你编写更可维护、更高效的前端代码。记住,设计模式是工具,而不是银弹。根据具体场景选择最合适的模式,并始终以代码清晰性和可维护性为首要考虑。
在实际项目中,这些模式经常组合使用。例如,一个API客户端可能同时使用中间件模式处理请求、管道模式处理数据转换、链式调用提供流畅API,并在适当的地方使用惰性加载优化性能。
希望这篇文章能帮助你在前端开发中更好地应用这些强大的模式!