前端常用模式:提升代码质量的四大核心模式

引言

在前端开发中,随着应用复杂度不断增加,代码的组织和管理变得越来越重要。本文将深入探讨前端开发中四种极其有用的模式:中间件模式、管道模式、链式调用和惰性加载。这些模式不仅能提高代码的可读性和可维护性,还能显著优化应用性能。

一、中间件模式(Middleware Pattern)

中间件模式允许我们在请求和响应的处理流程中插入多个处理阶段, 这种模式在Node.js框架(例如Koa、Express)中广泛应用, 但在前端同样有其用武之地。

1.1 核心概念

中间件本质上是一个函数, 它可以:

  • 访问请求(request)和响应(response)对象
  • 执行任何代码
  • 修改请求和响应对象
  • 结束请求-响应周期
  • 调用下一个中间件
1.2 基础实现
javascript 复制代码
// 简单中间件系统实现
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 错误处理中间件
javascript 复制代码
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 前端应用场景
javascript 复制代码
// 前端请求拦截中间件
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 基本实现
javascript 复制代码
// 同步管道
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 数据处理管道示例
javascript 复制代码
// 数据清洗管道
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 表单验证管道
javascript 复制代码
// 验证规则
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 基础链式调用
javascript 复制代码
// 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操作链式调用
javascript 复制代码
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 构建器模式与链式调用
javascript 复制代码
// 配置对象构建器
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 惰性求值实现
javascript 复制代码
// 惰性函数
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 图片懒加载
javascript 复制代码
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示例)
javascript 复制代码
// 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 数据懒加载(无限滚动)
javascript 复制代码
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 如何选择模式?
  1. 需要处理请求/响应流程 → 中间件模式
  2. 需要数据转换流水线 → 管道模式
  3. 需要流畅的API接口 → 链式调用
  4. 需要优化性能,延迟初始化 → 惰性加载

六、综合应用实例

6.1 完整的API客户端
javascript 复制代码
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 表单处理系统
javascript 复制代码
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 中间件模式最佳实践
  1. 保持中间件简洁单一: 每个中间件只做一件事
  2. 错误处理: 确保中间件有适当的错误处理机制
  3. 性能考虑: 避免在中间件中进行昂贵的同步操作
  4. 顺序很重要: 注意中间件的执行顺序对业务逻辑的影响
7.2 管道模式最佳实践
  1. 纯函数优先: 确保管道中的函数是纯函数,避免副作用
  2. 类型检查: 考虑添加运行时类型检查
  3. 错误处理: 现管道级别的错误处理机制
  4. 性能优化: 考虑使用流式处理大数据集
7.3 链式调用最佳实践
  1. 返回this: 确保每个链式方法都返回实例本身
  2. 不可变操作: 考虑实现不可变版本的链式调用
  3. 清晰的方法名: 方法名应该清晰表达其功能
  4. 文档完善: 链式调用可能隐藏复杂度,需要良好文档
7.4 惰性加载最佳实践
  1. 适度使用: 不要过度使用惰性加载,会增加复杂度
  2. 预加载策略: 对于可能很快需要的内容,考虑预加载
  3. 错误处理: 确保惰性加载失败时有降级方案
  4. 用户反馈: 加载过程中给用户适当的反馈

总结

这四种前端模式-中间件模式、管道模式、链式调用和惰性加载---都是现代前端开发中极其有用的工具。它们各自解决了不同的问题:

  • 中间件模式提供了处理复杂流程的模块化方式
  • 管道模式让数据转换变得清晰和可组合
  • 链式调用创造了流畅、易读的API
  • 惰性加载优化了性能和资源使用

掌握这些模式并知道何时使用它们,将帮助你编写更可维护、更高效的前端代码。记住,设计模式是工具,而不是银弹。根据具体场景选择最合适的模式,并始终以代码清晰性和可维护性为首要考虑。

在实际项目中,这些模式经常组合使用。例如,一个API客户端可能同时使用中间件模式处理请求、管道模式处理数据转换、链式调用提供流畅API,并在适当的地方使用惰性加载优化性能。

希望这篇文章能帮助你在前端开发中更好地应用这些强大的模式!

相关推荐
前端大卫2 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘18 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare19 分钟前
浅浅看一下设计模式
前端
Lee川22 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端