综合项目实践:小型框架/库全链路实现

引言

在当今的前端开发中,理解框架和库的内部原理比单纯使用它们更为重要。通过亲手实现核心功能,我们不仅能深入理解其设计思想,还能在面对复杂问题时提出更优的解决方案。本文将带你从零开始,完整实现一个小型但功能齐全的前端框架/库,涵盖MVVM、状态管理、路由、表单验证等核心模块,并拓展HTTP客户端、插件系统等高级特性。

一、实现简单的MVVM框架

MVVM(Model-View-ViewModel)是现代前端框架的核心模式。我们将实现一个响应式数据绑定系统,包含虚拟DOM和组件系统。

1.1 响应式数据系统
javascript 复制代码
// 观察者类
class Observer {
  constructor(data) {
    this.data = data;
    this.walk(data);
    this.dep = new Dep();
  }

  walk(obj, path = "") {
    Object.keys(obj).forEach((key) => {
      this.defineReactive(obj, key, obj[key], path);
    });
  }

  defineReactive(obj, key, val, path) {
    const propertyPath = path ? `${path}.${key}` : key;

    // 递归处理嵌套对象
    if (val && typeof val === "object" && !Array.isArray(val)) {
      new Observer(val);
    }

    const dep = new Dep();
    const that = this;

    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get() {
        // 收集依赖
        if (Dep.target) {
          dep.depend();
          // 收集父级依赖,用于嵌套对象
          if (that.dep && path === "") {
            that.dep.depend();
          }
        }
        return val;
      },
      set(newVal) {
        if (newVal === val) return;

        // 如果新值是对象,使其变为响应式
        if (newVal && typeof newVal === "object") {
          new Observer(newVal, propertyPath);
        }

        val = newVal;
        // 触发更新
        dep.notify();
      },
    });
  }
}

// 依赖收集器
class Dep {
  constructor() {
    this.subs = new Set();
  }

  addSub(sub) {
    this.subs.add(sub);
  }

  removeSub(sub) {
    this.subs.delete(sub);
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }

  notify() {
    this.subs.forEach((sub) => sub.update());
  }
}

Dep.target = null;

// 观察者
class Watcher {
  constructor(vm, exOrFn, cb, options = {}) {
    this.vm = vm;
    this.cb = cb;
    this.deps = new Set();
    this.depIds = new Set();
    this.lazy = options.lazy;
    this.dirty = this.lazy;

    if (typeof exOrFn === "function") {
      this.getter = exOrFn;
    } else {
      this.getter = this.parseGetter(exOrFn);
    }

    this.value = this.lazy ? undefined : this.get();
  }

  get() {
    Dep.target = this;
    const value = this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
  }

  addDep(dep) {
    const id = dep.id || Symbol();
    if (!this.depIds.has(id)) {
      this.depIds.add(id);
      this.deps.add(dep);
      dep.addSub(this);
    }
  }

  update() {
    if (this.lazy) {
      this.dirty = true;
    } else {
      this.run();
    }
  }

  run() {
    const value = this.get();
    const oldValue = this.value;
    this.value = value;
    this.cb.call(this.vm, value, oldValue);
  }

  evaluate() {
    this.value = this.get();
    this.dirty = false;
    return this.value;
  }

  parseGetter(exp) {
    if (/[^\w.$]/.test(exp)) return;
    const exps = exp.split(".");
    return function (obj) {
      for (let i = 0; i < exps.length; i++) {
        if (!obj) return;
        obj = obj[exps[i]];
      }
      return obj;
    };
  }
}
1.2 虚拟DOM和Diff算法
javascript 复制代码
const { update } = require("lodash");

// VNode类
class VNode {
  constructor(tag, data, children, text, elm) {
    this.tag = tag; // 标签名
    this.data = data || {}; // 节点数据
    this.children = children; // 子节点
    this.text = text; // 文本内容
    this.elm = elm; // 对应的真实DOM节点
    this.key = data && data.key; // 节点的key属性
  }

  isSameVNode(otherVNode) {
    return this.tag === otherVNode.tag && this.key === otherVNode.key;
  }
}

// 创建虚拟DOM
function createElement(tag, data, children) {
  if (Array.isArray(children) && children.length === 0) {
    children = undefined;
  }

  // 处理文本节点
  if (typeof children === "string" || typeof children === "number") {
    return createTextVNode(children);
  }

  // 处理子节点数组
  if (Array.isArray(children)) {
    children = children.map((child) => {
      if (typeof child === "string" || typeof child === "number") {
        return createTextVNode(child);
      }
      return child;
    });
  }

  return new VNode(tag, data, children);
}

function createTextVNode(text) {
  return new VNode(undefined, undefined, undefined, String(text));
}

// Diff算法实现
function patch(oldVNode, newVNode) {
  // 首次渲染
  if (!oldVNode) {
    return createElm(newVNode);
  }

  // 相同节点,进行patch
  if (sameVnode(oldVNode, newVNode)) {
    patchVNode(oldVNode, newVNode);
  } else {
    // 不同节点,直接替换
    const parent = oldVNode.elm.parentNode;
    const newElm = createElm(newVNode);
    parent.insertBefore(newElm, oldVNode.elm);
    parent.removeChild(oldVNode.elm);
  }

  return newVNode.elm;
}

function sameVnode(a, b) {
  return (
    a.key === b.key &&
    a.tag === b.tag &&
    a.data?.attrs?.id === b.data?.attrs?.id
  );
}

function createElm(vnode) {
  const { tag, children, text } = vnode;

  if (tag) {
    // 创建元素节点
    vnode.elm = document.createElement(tag);

    // 设置属性
    if (vnode.data) {
      setAttributes(vnode.elm, vnode.data.attrs || {});
      setEvents(vnode.elm, vnode.data.on || {});
    }

    // 递归创建子节点
    if (children) {
      children.forEach((child) => {
        const childElm = createElm(child);
        vnode.elm.appendChild(childElm);
      });
    }
  } else {
    // 创建文本节点
    vnode.elm = document.createTextNode(text);
  }

  return vnode.elm;
}

function patchValue(oldVNode, newVNode) {
  const elm = (newVNode.elm = oldVNode.elm);
  const oldCh = oldVNode.children;
  const newCh = newVNode.children;

  // 文本节点更新
  if (!newVNode.tag && oldVNode.text !== newVNode.text) {
    elm.textContent = newVNode.text;
    return;
  }

  // 更新属性
  if (newVNode.data) {
    updateAttributes(
      elm,
      oldVNode.data?.attrs || {},
      newVNode.data.attrs || {}
    );
    updateEvents(elm, oldVNode.data?.on || {}, newVNode.data.on || {});
  }

  // 更新子节点
  if (oldCh && newCh) {
    updateChildren(elm, oldCh, newCh);
  } else if (newCh) {
    // 添加新子节点
    newCh.forEach((child) => {
      const childElm = createElm(child);
      elm.appendChild(childElm);
    });
  } else if (oldCh) {
    // 删除旧子节点
    oldCh.forEach((child) => {
      elm.removeChild(child.elm);
    });
  }
}

function updateChildren(parentElm, oldCh, newCh) {
  let oldStartIdx = 0;
  let newStartIdx = 0;
  let oldEndIdx = oldCh.length - 1;
  let newEndIdx = newCh.length - 1;
  let oldStartVNode = oldCh[0];
  let oldEndVNode = oldCh[oldEndIdx];
  let newStartVNode = newCh[0];
  let newEndVNode = newCh[newEndIdx];

  while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    if (!oldStartVNode) {
      oldStartVNode = oldCh[++oldStartIdx];
    } else if (!oldEndVNode) {
      oldEndVNode = oldCh[--oldEndIdx];
    } else if (sameVnode(oldStartVNode, newStartVNode)) {
      patchValue(oldStartVNode, newStartVNode);
      oldStartVNode = oldCh[++oldStartIdx];
      newStartVNode = newCh[++newStartIdx];
    } else if (sameVnode(oldEndVNode, newEndVNode)) {
      patchValue(oldEndVNode, newEndVNode);
      oldEndVNode = oldCh[--oldEndIdx];
      newEndVNode = newCh[--newEndIdx];
    } else if (sameVnode(oldStartVNode, newEndVNode)) {
      patchValue(oldStartVNode, newEndVNode);
      parentElm.insertBefore(oldStartVNode.elm, oldEndVNode.elm.nextSibling);
      oldStartVNode = oldCh[++oldStartIdx];
      newEndVNode = newCh[--newEndIdx];
    } else if (sameVnode(oldEndVNode, newStartVNode)) {
      patchValue(oldEndVNode, newStartVNode);
      parentElm.insertBefore(oldEndVNode.elm, oldStartVNode.elm);
      oldEndVNode = oldCh[--oldEndIdx];
      newStartVNode = newCh[++newStartIdx];
    } else {
      // 创建新节点
      const newElm = createElm(newStartVNode);
      parentElm.insertBefore(newElm, oldStartVNode.elm);
      newStartVNode = newCh[++newStartIdx];
    }
  }

  // 添加剩余的新节点
  if (newStartIdx <= newEndIdx) {
    const before = newCh[newEndIdx + 1] ? newCh[newEndIdx + 1].elm : null;
    for (let i = newStartIdx; i <= newEndIdx; i++) {
      const newElm = createElm(newCh[i]);
      parentElm.insertBefore(newElm, before);
    }
  }

  // 删除剩余的旧节点
  if (oldStartIdx <= oldEndIdx) {
    for (let i = oldStartIdx; i <= oldEndIdx; i++) {
      if (oldCh[i]) {
        parentElm.removeChild(oldCh[i].elm);
      }
    }
  }
}
1.3 组件系统
javascript 复制代码
// 组件基类
class Component {
  constructor(options = {}) {
    this.$options = options;
    this.$data =
      typeof options.data === "function" ? options.data() : options.data || {};
    this.$props = options.props || {};
    this.$methods = options.methods || {};
    this.$computed = options.computed || {};
    this.$watch = options.watch || {};
    this.$el = null; // 组件的根DOM元素
    this.$parent = null; // 父组件实例
    this.$children = [];
    this.$refs = {};

    // 初始化响应式数据
    this._initState();
    this._initComputed();
    this._initWatch();
    this._initMethods();

    // 生命周期
    if (options.beforeCreate) options.beforeCreate.call(this);
    if (options.created) options.created.call(this);
  }

  _initState() {
    // 代理data到实例
    Object.keys(this.$data).forEach((key) => {
      Object.defineProperty(this, key, {
        get: () => this.$data[key],
        set: (val) => {
          this.$data[key] = val;
        },
      });
    });

    // 使data响应式
    new Observer(this.$data);
  }

  _initComputed() {
    const watchers = (this._computedWatchers = {});

    Object.keys(this.$computed).forEach((key) => {
      const getter = this.$computed[key];

      if (typeof getter === "function") {
        watchers[key] = new Watcher(this, getter, () => {}, { lazy: true });

        Object.defineProperty(this, key, {
          get: () => {
            const watcher = watchers[key];
            if (watcher.dirty) {
              watcher.evaluate();
            }
            return watcher.value;
          },
        });
      }
    });
  }

  _initWatch() {
    Object.keys(this.$watch).forEach((key) => {
      new Watcher(this, key, this.$watch[key]);
    });
  }

  _initMethods() {
    Object.keys(this.$methods).forEach((key) => {
      this[key] = this.$methods[key].bind(this);
    });
  }

  $mount(el) {
    this.$el = typeof el === "string" ? document.querySelector(el) : el;

    if (this.$options.beforeMount) {
      this.$options.beforeMount.call(this);
    }

    this._render();

    if (this.$options.mounted) {
      this.$options.mounted.call(this);
    }

    return this;
  }

  _render() {
    if (this.$options.render) {
      const vnode = this.$options.render.call(this, createElement);
      patch(this.$el, vnode);
    } else if (this.$options.template) {
      const template = this.$options.template;
      const vnode = this._compile(template);
      patch(this.$el, vnode);
    }
  }

  _compile(template) {
    // 简单的模板编译实现
    const ast = this._parse(template);
    return this._generate(ast);
  }

  $foreceUpdate() {
    this._render();
  }

  $emit(event, ...args) {
    const callbacks = this._events && this._events[event];
    if (callbacks) {
      callbacks.forEach((callback) => callback(...args));
    }
  }

  $on(event, callback) {
    if (!this._events) this._events = {};
    if (!this._events[event]) this._events[event] = [];
    this._events[event].push(callback);
  }

  $off(event, callback) {
    if (!this._events || !this._events[event]) return;

    if (callback) {
      const index = this._events[event].indexOf(callback);
      if (index > -1) {
        this._events[event].splice(index, 1);
      }
    } else {
      delete this._events[event];
    }
  }

  $nextTick(cb) {
    return Promise.resolve().then(cb);
  }
}

// 使用示例
class MyApp extends Component {
  constructor() {
    super({
      data() {
        return {
          message: "Hello MVVM",
          count: 0,
          items: ["Item 1", "Item 2", "Item 3"],
        };
      },

      computed: {
        reversedMessage() {
          return this.message.split("").reverse().join("");
        },

        doubleCount() {
          return this.count * 2;
        },
      },

      methods: {
        increment() {
          this.count++;
        },

        addItem() {
          this.items.push(`Item ${this.items.length + 1}`);
        },

        removeItem(index) {
          this.items.splice(index, 1);
        },
      },

      watch: {
        count(newVal, oldVal) {
          console.log(`count changed from ${oldVal} to ${newVal}`);
        },
      },

      created() {
        console.log("Component created");
      },

      mounted() {
        console.log("Component mounted");
      },

      render(h) {
        return h("div", { attrs: { id: "app" } }, [
          h("h1", this.message),
          h("p", `Count: ${this.count}, Double: ${this.doubleCount}`),
          h("p", `Reversed: ${this.reversedMessage}`),
          h("button", { on: { click: this.increment } }, "Increment"),
          h("button", { on: { click: this.addItem } }, "Add Item"),
          h(
            "ul",
            this.items.map((item, index) =>
              h("li", { key: index }, [
                item,
                h(
                  "button",
                  {
                    on: { click: () => this.removeItem(index) },
                  },
                  "Remove"
                ),
              ])
            )
          ),
        ]);
      },
    });
  }
}

// 启动应用
const app = new MyApp();
app.$mount("#app");

二、状态管理库(类似Redux)

2.1 核心Store实现
javascript 复制代码
// 创建Store
function createStore(reducer, preloadedState, enhancer) {
  // 处理中间件
  if (typeof enhancer === "function") {
    return enhancer(createStore)(reducer, preloadedState);
  }

  let state = preloadedState;
  let listeners = [];
  let isDispatching = false;

  // 获取当前状态
  function getState() {
    if (isDispatching) {
      throw new Error("Cannot call getState while dispatching");
    }
    return state;
  }

  // 订阅状态变化
  function subscribe(listener) {
    if (typeof listener !== "function") {
      throw new Error("Listener must be a function");
    }

    if (isDispatching) {
      throw new Error("Cannot subscribe while dispatching");
    }

    let isSubscribed = true;
    listeners.push(listener);

    // 返回取消订阅函数
    return function unsubscribe() {
      if (!isSubscribed) return;

      if (isDispatching) {
        throw new Error("Cannot unsubscribe while dispatching");
      }

      isSubscribed = false;
      const index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  // 分发action
  function dispatch(action) {
    if (typeof action !== "object" || action === null) {
      throw new Error("Action must be a plain object");
    }
    if (typeof action.type === "undefined") {
      throw new Error("Action must have a 'type' property");
    }
    if (isDispatching) {
      throw new Error("Cannot dispatch while dispatching");
    }

    try {
      isDispatching = true;
      state = reducer(state, action);
    } finally {
      isDispatching = false;
    }

    // 通知所有订阅者
    listeners.slice().forEach((listener) => listener());
    return action;
  }

  // 替换reducer
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Next reducer must be a function");
    }
    reducer = nextReducer;
    // 初始化状态
    dispatch({ type: "@@redux/INIT" });
  }

  // 初始化状态
  dispatch({ type: "@@redux/INIT" });

  return {
    getState,
    subscribe,
    dispatch,
    replaceReducer,
  };
}

// 组合多个reducer
function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers);
  const finalReducers = {};

  // 过滤掉非函数的reducer
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];
    if (typeof reducers[key] === "function") {
      finalReducers[key] = reducers[key];
    }
  }

  const finalReducersKeys = Object.keys(finalReducers);

  return function combination(state = {}, action) {
    let hasChanged = false;
    const nextState = {};

    for (let i = 0; i < finalReducersKeys.length; i++) {
      const key = finalReducersKeys[i];
      const reducer = finalReducers[key];
      const previousStateForKey = state[key];
      const nextStateForKey = reducer(previousStateForKey, action);
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }

    return hasChanged ? nextState : state;
  };
}

// 使用示例
const counterReducer = (state = 0, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    case "RESET":
      return 0;
    default:
      return state;
  }
};

const todoReducer = (state = [], action) => {
  switch (action.type) {
    case "ADD_TODO":
      return [
        ...state,
        {
          id: Date.now(),
          text: action.text,
          completed: false,
        },
      ];
    case "TOGGLE_TODO":
      return state.map((todo) =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
};

const rootReducer = combineReducers({
  counter: counterReducer,
  todos: todoReducer,
});

const store = createStore(rootReducer);

// 订阅状态变化
const unsubscribe = store.subscribe(() => {
  console.log("State changed:", store.getState());
});

// 分发action
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "ADD_TODO", text: "Learn Redux" });
2.2 中间件系统
javascript 复制代码
// 应用中间件
function applyMiddleware(...middlewares) {
  return createStore => (reducer, preloadedState) => {
    const store = createStore(reducer, preloadedState);
    let dispatch = () => {
      throw new Error('Cannot call dispatch while constructing middleware');
    };
    
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    };
    
    // 给每个中间件注入store API
    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    
    // 组合中间件
    dispatch = compose(...chain)(store.dispatch);
    
    return {
      ...store,
      dispatch
    };
  };
}

// 组合函数
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg;
  }
  
  if (funcs.length === 1) {
    return funcs[0];
  }
  
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

// 常用中间件实现

// 1. Logger中间件
const logger = store => next => action => {
  console.group(action.type);
  console.log('Previous state:', store.getState());
  console.log('Action:', action);
  
  const result = next(action);
  
  console.log('Next state:', store.getState());
  console.groupEnd();
  
  return result;
};

// 2. Thunk中间件(支持异步action)
const thunk = store => next => action => {
  if (typeof action === 'function') {
    return action(store.dispatch, store.getState);
  }
  
  return next(action);
};

// 3. Promise中间件
const promise = store => next => action => {
  if (typeof action.then === 'function') {
    return action.then(store.dispatch);
  }
  
  return next(action);
};

// 4. 错误处理中间件
const errorHandler = store => next => action => {
  try {
    return next(action);
  } catch (err) {
    console.error('Error in action:', action.type, err);
    
    // 可以分发错误action
    store.dispatch({
      type: 'ERROR_OCCURRED',
      error: err.message,
      originalAction: action
    });
    
    throw err;
  }
};

// 使用中间件
const storeWithMiddleware = createStore(
  rootReducer,
  applyMiddleware(thunk, logger, errorHandler)
);

// 异步action creator
function fetchTodos() {
  return async (dispatch, getState) => {
    dispatch({ type: 'FETCH_TODOS_REQUEST' });
    
    try {
      const response = await fetch('/api/todos');
      const todos = await response.json();
      
      dispatch({
        type: 'FETCH_TODOS_SUCCESS',
        todos
      });
    } catch (error) {
      dispatch({
        type: 'FETCH_TODOS_FAILURE',
        error: error.message
      });
    }
  };
}

// 使用异步action
storeWithMiddleware.dispatch(fetchTodos());
2.3 React绑定(类似React-Redux)
javascript 复制代码
// Context提供Store
const ReduxContext = React.createContext(null);

// Provider组件
class Provider extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      store: props.store
    };
  }
  
  render() {
    return (
      <ReduxContext.Provider value={this.state.store}>
        {this.props.children}
      </ReduxContext.Provider>
    );
  }
}

// connect高阶组件
function connect(mapStateToProps, mapDispatchToProps) {
  return WrappedComponent => {
    class ConnectedComponent extends React.Component {
      static contextType = ReduxContext;
      
      constructor(props, context) {
        super(props, context);
        this.store = context;
        
        this.state = {
          ...this.getStateFromStore()
        };
        
        this.handleChange = this.handleChange.bind(this);
      }
      
      componentDidMount() {
        this.unsubscribe = this.store.subscribe(this.handleChange);
      }
      
      componentWillUnmount() {
        this.unsubscribe();
      }
      
      getStateFromStore() {
        const state = this.store.getState();
        const mappedState = mapStateToProps ? mapStateToProps(state, this.props) : {};
        const mappedDispatch = mapDispatchToProps 
          ? mapDispatchToProps(this.store.dispatch, this.props)
          : { dispatch: this.store.dispatch };
        
        return {
          ...mappedState,
          ...mappedDispatch
        };
      }
      
      handleChange() {
        this.setState(this.getStateFromStore());
      }
      
      render() {
        return <WrappedComponent {...this.props} {...this.state} />;
      }
    }
    
    return ConnectedComponent;
  };
}

// useSelector Hook
function useSelector(selector) {
  const store = React.useContext(ReduxContext);
  const [state, setState] = React.useState(() => selector(store.getState()));
  
  React.useEffect(() => {
    const unsubscribe = store.subscribe(() => {
      const newState = selector(store.getState());
      setState(newState);
    });
    
    return unsubscribe;
  }, [store, selector]);
  
  return state;
}

// useDispatch Hook
function useDispatch() {
  const store = React.useContext(ReduxContext);
  return store.dispatch;
}

// 使用示例
const TodoList = ({ todos, addTodo, toggleTodo }) => (
  <div>
    <button onClick={() => addTodo('New Todo')}>Add Todo</button>
    <ul>
      {todos.map(todo => (
        <li
          key={todo.id}
          onClick={() => toggleTodo(todo.id)}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  </div>
);

const mapStateToProps = state => ({
  todos: state.todos
});

const mapDispatchToProps = dispatch => ({
  addTodo: text => dispatch({ type: 'ADD_TODO', text }),
  toggleTodo: id => dispatch({ type: 'TOGGLE_TODO', id })
});

const ConnectedTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);

// 或者使用Hooks
function TodoListHook() {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();
  
  return (
    <div>
      <button onClick={() => dispatch({ type: 'ADD_TODO', text: 'New Todo' })}>
        Add Todo
      </button>
      <ul>
        {todos.map(todo => (
          <li
            key={todo.id}
            onClick={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

// 应用入口
function App() {
  return (
    <Provider store={store}>
      <ConnectedTodoList />
      <TodoListHook />
    </Provider>
  );
}

三、路由库实现

3.1 Hash路由实现
javascript 复制代码
class HashRouter {
  constructor(routes = [], options = {}) {
    this.routes = new Map();
    this.current = null;
    this.history = [];
    this.maxHistory = options.maxHistory || 50;
    this.middlewares = [];
    
    // 初始化路由
    routes.forEach(route => {
      this.addRoute(route.path, route.component, route.meta);
    });
    
    // 监听hash变化
    window.addEventListener('hashchange', this.handleHashChange.bind(this));
    
    // 初始路由
    this.handleHashChange();
  }
  
  addRoute(path, component, meta = {}) {
    const keys = [];
    const regex = this.pathToRegex(path, keys);
    
    this.routes.set(regex, {
      component,
      meta,
      keys
    });
  }
  
  pathToRegex(path, keys) {
    path = path
      .replace(/\/:(\w+)/g, (_, key) => {
        keys.push(key);
        return '/([^/]+)';
      })
      .replace(/\*/g, '(.*)');
    
    return new RegExp(`^${path}$`);
  }
  
  handleHashChange() {
    const hash = window.location.hash.slice(1) || '/';
    this.navigateTo(hash, false);
  }
  
  async navigateTo(path, updateHash = true) {
    // 执行中间件
    for (const middleware of this.middlewares) {
      const result = await middleware({ from: this.current, to: path });
      if (result === false) {
        return false; // 阻止导航
      }
    }
    
    // 匹配路由
    let match = null;
    let params = {};
    
    for (const [regex, route] of this.routes) {
      const result = path.match(regex);
      if (result) {
        match = route;
        
        // 提取参数
        route.keys.forEach((key, index) => {
          params[key] = result[index + 1];
        });
        
        break;
      }
    }
    
    if (!match) {
      // 默认路由或404
      match = this.routes.get(/.*/);
      if (!match) {
        console.error(`Route not found: ${path}`);
        return false;
      }
    }
    
    // 更新hash
    if (updateHash && window.location.hash.slice(1) !== path) {
      window.location.hash = path;
    }
    
    // 保存历史记录
    this.history.push({
      path,
      params,
      meta: match.meta,
      timestamp: Date.now()
    });
    
    // 限制历史记录大小
    if (this.history.length > this.maxHistory) {
      this.history.shift();
    }
    
    // 更新当前路由
    const previous = this.current;
    this.current = {
      path,
      params,
      component: match.component,
      meta: match.meta
    };
    
    // 触发路由变化事件
    this.emit('routeChange', { from: previous, to: this.current });
    
    return true;
  }
  
  go(n) {
    const targetIndex = this.history.length - 1 + n;
    if (targetIndex >= 0 && targetIndex < this.history.length) {
      const target = this.history[targetIndex];
      this.navigateTo(target.path);
    }
  }
  
  back() {
    this.go(-1);
  }
  
  forward() {
    this.go(1);
  }
  
  use(middleware) {
    this.middlewares.push(middleware);
  }
  
  // 事件系统
  on(event, callback) {
    if (!this._events) this._events = {};
    if (!this._events[event]) this._events[event] = [];
    this._events[event].push(callback);
  }
  
  emit(event, data) {
    if (this._events && this._events[event]) {
      this._events[event].forEach(callback => callback(data));
    }
  }
}

// 使用示例
const router = new HashRouter([
  {
    path: '/',
    component: HomePage,
    meta: { requiresAuth: false }
  },
  {
    path: '/about',
    component: AboutPage,
    meta: { requiresAuth: false }
  },
  {
    path: '/users/:id',
    component: UserPage,
    meta: { requiresAuth: true }
  },
  {
    path: '/settings',
    component: SettingsPage,
    meta: { requiresAuth: true }
  },
  {
    path: '*',
    component: NotFoundPage
  }
]);

// 添加中间件
router.use(async ({ from, to }) => {
  console.log(`Navigating from ${from?.path || 'none'} to ${to}`);
  
  // 检查是否需要认证
  const route = Array.from(router.routes.values())
    .find(r => to.match(r.regex));
    
  if (route?.meta.requiresAuth && !isAuthenticated()) {
    console.log('Authentication required, redirecting to login');
    router.navigateTo('/login');
    return false;
  }
  
  return true;
});

// 导航
router.navigateTo('/users/123');
3.2 History路由实现
javascript 复制代码
class HistoryRouter {
  constructor(routes = [], options = {}) {
    this.routes = new Map();
    this.current = null;
    this.history = window.history;
    this.middlewares = [];
    
    // 配置
    this.base = options.base || '';
    this.mode = options.mode || 'history';
    
    // 初始化路由
    routes.forEach(route => {
      this.addRoute(route.path, route.component, route.meta);
    });
    
    // 监听popstate事件
    window.addEventListener('popstate', this.handlePopState.bind(this));
    
    // 初始路由
    this.handlePopState();
  }
  
  addRoute(path, component, meta = {}) {
    const keys = [];
    const regex = this.pathToRegex(path, keys);
    
    this.routes.set(regex, {
      component,
      meta,
      keys,
      path
    });
  }
  
  pathToRegex(path, keys) {
    path = path
      .replace(/\/:(\w+)/g, (_, key) => {
        keys.push(key);
        return '/([^/]+)';
      })
      .replace(/\*/g, '(.*)');
    
    return new RegExp(`^${path}$`);
  }
  
  getCurrentPath() {
    let path = decodeURI(window.location.pathname);
    
    if (this.base && path.startsWith(this.base)) {
      path = path.slice(this.base.length);
    }
    
    return path || '/';
  }
  
  async handlePopState() {
    const path = this.getCurrentPath();
    await this.navigateTo(path, false);
  }
  
  async navigateTo(path, updateHistory = true) {
    // 规范化路径
    if (!path.startsWith('/')) {
      path = '/' + path;
    }
    
    // 执行中间件
    for (const middleware of this.middlewares) {
      const result = await middleware({ from: this.current, to: path });
      if (result === false) {
        return false;
      }
    }
    
    // 匹配路由
    let match = null;
    let params = {};
    let matchedPath = '';
    
    for (const [regex, route] of this.routes) {
      const result = path.match(regex);
      if (result) {
        match = route;
        matchedPath = route.path;
        
        // 提取参数
        route.keys.forEach((key, index) => {
          params[key] = result[index + 1];
        });
        
        break;
      }
    }
    
    if (!match) {
      // 默认路由或404
      match = this.routes.get(/.*/);
      if (!match) {
        console.error(`Route not found: ${path}`);
        return false;
      }
    }
    
    // 更新history
    if (updateHistory) {
      const fullPath = this.base + path;
      
      if (this.current && this.current.path === path) {
        // 相同路径,替换当前记录
        this.history.replaceState(
          { path, params, meta: match.meta },
          '',
          fullPath
        );
      } else {
        // 添加新记录
        this.history.pushState(
          { path, params, meta: match.meta },
          '',
          fullPath
        );
      }
    }
    
    // 更新当前路由
    const previous = this.current;
    this.current = {
      path,
      matchedPath,
      params,
      component: match.component,
      meta: match.meta,
      fullPath: this.base + path
    };
    
    // 触发路由变化
    this.emit('routeChange', { from: previous, to: this.current });
    
    // 更新页面标题
    if (match.meta.title) {
      document.title = match.meta.title;
    }
    
    return true;
  }
  
  replace(path) {
    this.navigateTo(path, true);
  }
  
  go(n) {
    this.history.go(n);
  }
  
  back() {
    this.history.back();
  }
  
  forward() {
    this.history.forward();
  }
  
  use(middleware) {
    this.middlewares.push(middleware);
  }
  
  // 链接拦截
  interceptLinks(container = document) {
    container.addEventListener('click', event => {
      const link = event.target.closest('a');
      
      if (!link || link.target === '_blank' || link.hasAttribute('download')) {
        return;
      }
      
      const href = link.getAttribute('href');
      
      if (href && href.startsWith('/') && !href.startsWith('//')) {
        event.preventDefault();
        this.navigateTo(href);
      }
    });
  }
  
  // 生成URL
  generate(path, params = {}) {
    let result = path;
    
    // 替换参数
    Object.keys(params).forEach(key => {
      result = result.replace(`:${key}`, encodeURIComponent(params[key]));
    });
    
    return this.base + result;
  }
}

// 路由视图组件
class RouterView extends HTMLElement {
  constructor() {
    super();
    this.router = null;
    this.currentComponent = null;
  }
  
  connectedCallback() {
    // 获取router实例
    this.router = window.__router__;
    
    if (!this.router) {
      console.error('Router not found');
      return;
    }
    
    // 监听路由变化
    this.router.on('routeChange', this.updateView.bind(this));
    
    // 初始渲染
    this.updateView({ to: this.router.current });
  }
  
  updateView({ to }) {
    if (!to || !to.component) return;
    
    // 清理旧组件
    if (this.currentComponent && this.currentComponent.unmount) {
      this.currentComponent.unmount();
    }
    
    // 渲染新组件
    this.innerHTML = '';
    this.currentComponent = to.component;
    
    if (typeof to.component === 'function') {
      // 函数组件
      const element = to.component({ route: to });
      if (element) {
        this.appendChild(element);
      }
    } else if (to.component.render) {
      // 类组件
      to.component.render({ route: to, parent: this });
    } else {
      // 字符串模板
      this.innerHTML = to.component;
    }
  }
  
  disconnectedCallback() {
    if (this.router) {
      this.router.off('routeChange', this.updateView);
    }
  }
}

// 自定义元素注册
customElements.define('router-view', RouterView);

// 链接组件
class RouterLink extends HTMLElement {
  constructor() {
    super();
    this.router = null;
    this.to = null;
    this.activeClass = 'active';
  }
  
  static get observedAttributes() {
    return ['to', 'active-class'];
  }
  
  connectedCallback() {
    this.router = window.__router__;
    this.to = this.getAttribute('to');
    this.activeClass = this.getAttribute('active-class') || 'active';
    
    if (!this.router || !this.to) return;
    
    this.addEventListener('click', this.handleClick.bind(this));
    this.updateActiveState();
    
    // 监听路由变化更新激活状态
    this.router.on('routeChange', this.updateActiveState.bind(this));
  }
  
  handleClick(event) {
    event.preventDefault();
    this.router.navigateTo(this.to);
  }
  
  updateActiveState() {
    const currentPath = this.router.current?.path;
    
    if (currentPath === this.to) {
      this.classList.add(this.activeClass);
    } else {
      this.classList.remove(this.activeClass);
    }
  }
  
  disconnectedCallback() {
    if (this.router) {
      this.router.off('routeChange', this.updateActiveState);
    }
  }
}

customElements.define('router-link', RouterLink);

四、表单验证库

4.1 验证器核心
javascript 复制代码
class Validator {
  constructor(rules = {}, options = {}) {
    this.rules = this.normalizeRules(rules);
    this.options = {
      stopOnFirstError: false,
      validateOnChange: true,
      validateOnBlur: true,
      ...options
    };
    
    this.errors = new Map();
    this.validators = this.createBuiltInValidators();
    this.customValidators = new Map();
    this.asyncValidators = new Map();
    this.isValidating = false;
  }
  
  normalizeRules(rules) {
    const normalized = {};
    
    Object.keys(rules).forEach(field => {
      const rule = rules[field];
      
      if (Array.isArray(rule)) {
        normalized[field] = rule;
      } else if (typeof rule === 'object' && rule !== null) {
        normalized[field] = [rule];
      } else if (typeof rule === 'string') {
        normalized[field] = rule.split('|').map(r => ({ type: r }));
      }
    });
    
    return normalized;
  }
  
  createBuiltInValidators() {
    return {
      required: (value, param, field) => {
        if (value === undefined || value === null || value === '') {
          return 'This field is required';
        }
        return true;
      },
      
      email: (value) => {
        const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (value && !regex.test(value)) {
          return 'Invalid email address';
        }
        return true;
      },
      
      min: (value, param) => {
        const num = Number(value);
        if (!isNaN(num) && num < Number(param)) {
          return `Minimum value is ${param}`;
        }
        return true;
      },
      
      max: (value, param) => {
        const num = Number(value);
        if (!isNaN(num) && num > Number(param)) {
          return `Maximum value is ${param}`;
        }
        return true;
      },
      
      minLength: (value, param) => {
        if (value && value.length < Number(param)) {
          return `Minimum length is ${param}`;
        }
        return true;
      },
      
      maxLength: (value, param) => {
        if (value && value.length > Number(param)) {
          return `Maximum length is ${param}`;
        }
        return true;
      },
      
      pattern: (value, param) => {
        const regex = new RegExp(param);
        if (value && !regex.test(value)) {
          return 'Invalid format';
        }
        return true;
      },
      
      equals: (value, param, field, data) => {
        if (value !== data[param]) {
          return `Must match ${param}`;
        }
        return true;
      },
      
      custom: (value, param, field, data) => {
        const validator = this.customValidators.get(param);
        if (validator) {
          return validator(value, field, data);
        }
        return true;
      },
      
      async: async (value, param, field, data) => {
        const validator = this.asyncValidators.get(param);
        if (validator) {
          try {
            return await validator(value, field, data);
          } catch (error) {
            return error.message || 'Validation failed';
          }
        }
        return true;
      }
    };
  }
  
  addValidator(name, validator, isAsync = false) {
    if (isAsync) {
      this.asyncValidators.set(name, validator);
    } else {
      this.customValidators.set(name, validator);
    }
  }
  
  async validateField(field, value, data = {}) {
    const rules = this.rules[field];
    
    if (!rules) {
      this.errors.delete(field);
      return true;
    }
    
    const fieldErrors = [];
    
    for (const rule of rules) {
      const { type, message, params = {}, ...options } = rule;
      
      let validator = this.validators[type];
      
      if (!validator) {
        console.warn(`Validator "${type}" not found for field "${field}"`);
        continue;
      }
      
      const param = options.value || options;
      
      try {
        const result = await validator(value, param, field, data);
        
        if (result !== true) {
          fieldErrors.push({
            type,
            message: message || result,
            params: param
          });
          
          if (this.options.stopOnFirstError) {
            break;
          }
        }
      } catch (error) {
        fieldErrors.push({
          type,
          message: error.message || 'Validation error',
          params: param
        });
      }
    }
    
    if (fieldErrors.length > 0) {
      this.errors.set(field, fieldErrors);
      return false;
    } else {
      this.errors.delete(field);
      return true;
    }
  }
  
  async validate(data, fields = null) {
    this.isValidating = true;
    const fieldList = fields || Object.keys(this.rules);
    const results = [];
    
    for (const field of fieldList) {
      const value = data[field];
      const isValid = await this.validateField(field, value, data);
      results.push(isValid);
    }
    
    this.isValidating = false;
    return results.every(result => result === true);
  }
  
  getErrors(field = null) {
    if (field) {
      return this.errors.get(field) || [];
    }
    
    const allErrors = {};
    this.errors.forEach((errors, fieldName) => {
      allErrors[fieldName] = errors;
    });
    
    return allErrors;
  }
  
  getFirstError(field) {
    const errors = this.getErrors(field);
    return errors.length > 0 ? errors[0] : null;
  }
  
  clearErrors(field = null) {
    if (field) {
      this.errors.delete(field);
    } else {
      this.errors.clear();
    }
  }
  
  setErrors(field, errors) {
    if (errors && errors.length > 0) {
      this.errors.set(field, errors);
    } else {
      this.errors.delete(field);
    }
  }
  
  hasErrors(field = null) {
    if (field) {
      return this.errors.has(field);
    }
    return this.errors.size > 0;
  }
}

// 使用示例
const validator = new Validator({
  username: [
    { type: 'required', message: 'Username is required' },
    { type: 'minLength', value: 3, message: 'Minimum 3 characters' },
    { type: 'maxLength', value: 20, message: 'Maximum 20 characters' },
    { 
      type: 'custom', 
      value: 'usernameAvailable',
      message: 'Username already taken'
    }
  ],
  
  email: [
    { type: 'required', message: 'Email is required' },
    { type: 'email', message: 'Invalid email format' }
  ],
  
  password: [
    { type: 'required', message: 'Password is required' },
    { type: 'minLength', value: 8, message: 'Minimum 8 characters' },
    { 
      type: 'pattern', 
      value: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$',
      message: 'Must contain uppercase, lowercase and number'
    }
  ],
  
  confirmPassword: [
    { type: 'required', message: 'Please confirm password' },
    { type: 'equals', value: 'password', message: 'Passwords do not match' }
  ],
  
  age: [
    { type: 'min', value: 18, message: 'Must be at least 18 years old' },
    { type: 'max', value: 120, message: 'Invalid age' }
  ]
});

// 添加自定义验证器
validator.addValidator('usernameAvailable', async (value) => {
  // 模拟异步检查
  const response = await fetch(`/api/check-username?username=${value}`);
  const data = await response.json();
  
  if (!data.available) {
    return 'Username is already taken';
  }
  
  return true;
}, true);

// 验证数据
const formData = {
  username: 'john123',
  email: 'john@example.com',
  password: 'Password123',
  confirmPassword: 'Password123',
  age: 25
};

validator.validate(formData).then(isValid => {
  console.log('Form valid:', isValid);
  console.log('Errors:', validator.getErrors());
});
4.2 表单绑定与集成
javascript 复制代码
class Form {
  constructor(options = {}) {
    this.fields = options.fields || {};
    this.values = this.initialValues(options.initialValues || {});
    this.errors = {};
    this.touched = new Set();
    this.submitting = false;
    this.submitCount = 0;
    
    this.validator = options.validator || new Validator(options.rules || {});
    this.onSubmit = options.onSubmit;
    this.onChange = options.onChange;
    this.onValidate = options.onValidate;
    
    // 事件监听器
    this.listeners = {
      change: new Set(),
      submit: new Set(),
      error: new Set(),
      validate: new Set()
    };
  }
  
  initialValues(initial) {
    const values = {};
    
    Object.keys(this.fields).forEach(key => {
      const field = this.fields[key];
      values[key] = initial[key] !== undefined ? initial[key] : field.initialValue;
    });
    
    return values;
  }
  
  setFieldValue(field, value, validate = true) {
    const oldValue = this.values[field];
    this.values[field] = value;
    
    // 触发变更事件
    this.emit('change', { field, value, oldValue });
    
    if (this.onChange) {
      this.onChange(field, value, oldValue);
    }
    
    // 自动验证
    if (validate) {
      this.validateField(field);
    }
    
    return this;
  }
  
  setValues(values, validate = true) {
    Object.keys(values).forEach(field => {
      this.setFieldValue(field, values[field], false);
    });
    
    if (validate) {
      this.validate();
    }
    
    return this;
  }
  
  getFieldValue(field) {
    return this.values[field];
  }
  
  getValues() {
    return { ...this.values };
  }
  
  reset(values = {}) {
    this.values = this.initialValues(values);
    this.errors = {};
    this.touched.clear();
    this.submitting = false;
    this.submitCount = 0;
    this.validator.clearErrors();
    
    this.emit('change', { type: 'reset', values: this.values });
    
    return this;
  }
  
  touchField(field) {
    this.touched.add(field);
    return this;
  }
  
  touchAll() {
    Object.keys(this.values).forEach(field => {
      this.touched.add(field);
    });
    
    return this;
  }
  
  isTouched(field) {
    if (field) {
      return this.touched.has(field);
    }
    return this.touched.size > 0;
  }
  
  async validateField(field) {
    const value = this.values[field];
    const isValid = await this.validator.validateField(field, value, this.values);
    
    if (isValid) {
      delete this.errors[field];
    } else {
      this.errors[field] = this.validator.getErrors(field);
    }
    
    this.emit('validate', { field, isValid, errors: this.errors[field] });
    
    if (this.onValidate) {
      this.onValidate(field, isValid, this.errors[field]);
    }
    
    return isValid;
  }
  
  async validate(fields = null) {
    const isValid = await this.validator.validate(this.values, fields);
    
    // 更新错误状态
    const errors = this.validator.getErrors();
    this.errors = errors;
    
    this.emit('validate', { isValid, errors });
    
    return isValid;
  }
  
  getFieldError(field) {
    return this.errors[field] || [];
  }
  
  hasErrors(field = null) {
    if (field) {
      return !!this.errors[field];
    }
    return Object.keys(this.errors).length > 0;
  }
  
  async submit() {
    this.submitting = true;
    this.submitCount++;
    this.touchAll();
    
    // 验证所有字段
    const isValid = await this.validate();
    
    if (!isValid) {
      this.submitting = false;
      this.emit('error', { errors: this.errors });
      return Promise.reject({ errors: this.errors, values: this.values });
    }
    
    try {
      const result = this.onSubmit 
        ? await this.onSubmit(this.values, this)
        : await this.defaultSubmit(this.values);
      
      this.submitting = false;
      this.emit('submit', { values: this.values, result });
      
      return result;
    } catch (error) {
      this.submitting = false;
      this.emit('error', { error, values: this.values });
      throw error;
    }
  }
  
  defaultSubmit(values) {
    return Promise.resolve(values);
  }
  
  // 事件系统
  on(event, callback) {
    if (this.listeners[event]) {
      this.listeners[event].add(callback);
    }
    
    return () => this.off(event, callback);
  }
  
  off(event, callback) {
    if (this.listeners[event]) {
      this.listeners[event].delete(callback);
    }
  }
  
  emit(event, data) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(callback => callback(data));
    }
  }
}

// React表单Hook
function useForm(options = {}) {
  const [form] = React.useState(() => new Form(options));
  const [values, setValues] = React.useState(form.getValues());
  const [errors, setErrors] = React.useState({});
  const [touched, setTouched] = React.useState({});
  const [submitting, setSubmitting] = React.useState(false);
  
  // 监听表单变化
  React.useEffect(() => {
    const unsubscribe = form.on('change', () => {
      setValues(form.getValues());
    });
    
    return unsubscribe;
  }, [form]);
  
  React.useEffect(() => {
    const unsubscribe = form.on('validate', ({ errors: newErrors }) => {
      setErrors(newErrors);
    });
    
    return unsubscribe;
  }, [form]);
  
  React.useEffect(() => {
    const unsubscribe = form.on('submit', () => {
      setSubmitting(false);
    });
    
    return unsubscribe;
  }, [form]);
  
  const handleChange = (field) => (event) => {
    const value = event.target.type === 'checkbox' 
      ? event.target.checked 
      : event.target.value;
    
    form.setFieldValue(field, value);
  };
  
  const handleBlur = (field) => () => {
    form.touchField(field);
    form.validateField(field);
    setTouched({ ...touched, [field]: true });
  };
  
  const handleSubmit = (callback) => async (event) => {
    if (event) event.preventDefault();
    
    setSubmitting(true);
    
    try {
      const result = await form.submit();
      if (callback) callback(result, form);
    } catch (error) {
      console.error('Form submission error:', error);
    }
  };
  
  const register = (field, options = {}) => {
    return {
      name: field,
      value: values[field] || '',
      onChange: handleChange(field),
      onBlur: handleBlur(field),
      ...options
    };
  };
  
  return {
    values,
    errors,
    touched,
    submitting,
    form,
    handleChange,
    handleBlur,
    handleSubmit,
    register,
    setFieldValue: form.setFieldValue.bind(form),
    setValues: form.setValues.bind(form),
    validate: form.validate.bind(form),
    reset: form.reset.bind(form),
    getFieldError: form.getFieldError.bind(form),
    hasErrors: form.hasErrors.bind(form)
  };
}

// 使用示例
function LoginForm() {
  const { register, handleSubmit, errors, submitting } = useForm({
    initialValues: {
      email: '',
      password: '',
      remember: false
    },
    
    rules: {
      email: [
        { type: 'required', message: 'Email is required' },
        { type: 'email', message: 'Invalid email format' }
      ],
      password: [
        { type: 'required', message: 'Password is required' },
        { type: 'minLength', value: 6, message: 'Minimum 6 characters' }
      ]
    },
    
    onSubmit: async (values) => {
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(values)
      });
      
      if (!response.ok) {
        throw new Error('Login failed');
      }
      
      return response.json();
    }
  });
  
  return (
    <form onSubmit={handleSubmit()}>
      <div>
        <label>Email</label>
        <input
          type="email"
          {...register('email')}
        />
        {errors.email && (
          <div className="error">{errors.email[0].message}</div>
        )}
      </div>
      
      <div>
        <label>Password</label>
        <input
          type="password"
          {...register('password')}
        />
        {errors.password && (
          <div className="error">{errors.password[0].message}</div>
        )}
      </div>
      
      <div>
        <label>
          <input
            type="checkbox"
            {...register('remember')}
          />
          Remember me
        </label>
      </div>
      
      <button type="submit" disabled={submitting}>
        {submitting ? 'Logging in...' : 'Login'}
      </button>
    </form>
  );
}
五、HTTP客户端(类似Axios)
javascript 复制代码
class HttpClient {
  constructor(config = {}) {
    this.defaults = {
      baseURL: '',
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
        ...config.headers
      },
      ...config
    };
    
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager()
    };
  }
  
  async request(config) {
    // 合并配置
    const requestConfig = this.mergeConfig(this.defaults, config);
    
    // 构建请求链
    const chain = [this.dispatchRequest, undefined];
    let promise = Promise.resolve(requestConfig);
    
    // 添加请求拦截器
    this.interceptors.request.forEach(interceptor => {
      chain.unshift(interceptor.fulfilled, interceptor.rejected);
    });
    
    // 添加响应拦截器
    this.interceptors.response.forEach(interceptor => {
      chain.push(interceptor.fulfilled, interceptor.rejected);
    });
    
    // 执行拦截器链
    while (chain.length) {
      promise = promise.then(chain.shift(), chain.shift());
    }
    
    return promise;
  }
  
  async dispatchRequest(config) {
    // 创建AbortController支持取消请求
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), config.timeout);
    
    // 构建URL
    let url = config.url;
    if (config.baseURL && !url.startsWith('http')) {
      url = config.baseURL.replace(/\/$/, '') + '/' + url.replace(/^\//, '');
    }
    
    // 处理参数
    if (config.params) {
      const params = new URLSearchParams();
      Object.keys(config.params).forEach(key => {
        const value = config.params[key];
        if (value !== null && value !== undefined) {
          params.append(key, value);
        }
      });
      
      const queryString = params.toString();
      if (queryString) {
        url += (url.includes('?') ? '&' : '?') + queryString;
      }
    }
    
    // 准备请求选项
    const options = {
      method: config.method || 'GET',
      headers: { ...config.headers },
      signal: controller.signal,
      credentials: config.withCredentials ? 'include' : 'same-origin'
    };
    
    // 处理请求体
    if (config.data) {
      if (options.headers['Content-Type'] === 'application/json') {
        options.body = JSON.stringify(config.data);
      } else if (options.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
        const formData = new URLSearchParams();
        Object.keys(config.data).forEach(key => {
          formData.append(key, config.data[key]);
        });
        options.body = formData;
      } else {
        options.body = config.data;
      }
    }
    
    try {
      const response = await fetch(url, options);
      clearTimeout(timeoutId);
      
      // 处理响应
      let data;
      const contentType = response.headers.get('content-type');
      
      if (contentType && contentType.includes('application/json')) {
        data = await response.json();
      } else if (contentType && contentType.includes('text/')) {
        data = await response.text();
      } else {
        data = await response.blob();
      }
      
      const result = {
        data,
        status: response.status,
        statusText: response.statusText,
        headers: this.parseHeaders(response.headers),
        config,
        request: response
      };
      
      // 处理HTTP错误状态码
      if (!response.ok) {
        throw this.createError(
          `Request failed with status code ${response.status}`,
          config,
          response.status,
          response,
          data
        );
      }
      
      return result;
    } catch (error) {
      clearTimeout(timeoutId);
      
      if (error.name === 'AbortError') {
        error = this.createError(
          `Timeout of ${config.timeout}ms exceeded`,
          config,
          'ECONNABORTED',
          null,
          null
        );
      }
      
      throw error;
    }
  }
  
  createError(message, config, code, request, response) {
    const error = new Error(message);
    error.config = config;
    error.code = code;
    error.request = request;
    error.response = response;
    error.isAxiosError = true;
    return error;
  }
  
  parseHeaders(headers) {
    const parsed = {};
    headers.forEach((value, key) => {
      parsed[key] = value;
    });
    return parsed;
  }
  
  mergeConfig(config1, config2) {
    const config = { ...config1 };
    
    Object.keys(config2).forEach(key => {
      if (key === 'headers') {
        config.headers = this.mergeHeaders(config1.headers, config2.headers);
      } else {
        config[key] = config2[key];
      }
    });
    
    return config;
  }
  
  mergeHeaders(headers1, headers2) {
    const headers = { ...headers1 };
    
    Object.keys(headers2).forEach(key => {
      if (headers2[key] !== undefined) {
        headers[key] = headers2[key];
      }
    });
    
    return headers;
  }
  
  // 快捷方法
  get(url, config = {}) {
    return this.request({ ...config, method: 'GET', url });
  }
  
  post(url, data, config = {}) {
    return this.request({ ...config, method: 'POST', url, data });
  }
  
  put(url, data, config = {}) {
    return this.request({ ...config, method: 'PUT', url, data });
  }
  
  delete(url, config = {}) {
    return this.request({ ...config, method: 'DELETE', url });
  }
  
  patch(url, data, config = {}) {
    return this.request({ ...config, method: 'PATCH', url, data });
  }
  
  // 创建实例
  create(config) {
    return new HttpClient(config);
  }
  
  // 并发请求
  all(promises) {
    return Promise.all(promises);
  }
  
  spread(callback) {
    return function(arr) {
      return callback.apply(null, arr);
    };
  }
}

// 拦截器管理器
class InterceptorManager {
  constructor() {
    this.handlers = [];
  }
  
  use(fulfilled, rejected) {
    this.handlers.push({
      fulfilled,
      rejected
    });
    
    return this.handlers.length - 1;
  }
  
  eject(id) {
    if (this.handlers[id]) {
      this.handlers[id] = null;
    }
  }
  
  forEach(fn) {
    this.handlers.forEach(handler => {
      if (handler !== null) {
        fn(handler);
      }
    });
  }
}

// 使用示例
const http = new HttpClient({
  baseURL: 'https://api.example.com',
  timeout: 5000,
  headers: {
    'X-Requested-With': 'XMLHttpRequest'
  }
});

// 添加请求拦截器
http.interceptors.request.use(
  config => {
    // 添加认证token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);

// 添加响应拦截器
http.interceptors.response.use(
  response => response,
  error => {
    if (error.response && error.response.status === 401) {
      // 处理未授权
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

// 使用HTTP客户端
async function fetchData() {
  try {
    const response = await http.get('/users', {
      params: { page: 1, limit: 10 }
    });
    
    console.log('Users:', response.data);
    
    // 创建用户
    const createResponse = await http.post('/users', {
      name: 'John Doe',
      email: 'john@example.com'
    });
    
    console.log('Created user:', createResponse.data);
  } catch (error) {
    console.error('Request failed:', error);
  }
}

六、插件系统与生态系统

javascript 复制代码
// 插件系统基类
class PluginSystem {
  constructor() {
    this.plugins = new Map();
    this.hooks = new Map();
    this.middlewares = [];
  }
  
  // 注册插件
  use(plugin, options = {}) {
    if (typeof plugin === 'function') {
      plugin(this, options);
    } else if (plugin && typeof plugin.install === 'function') {
      plugin.install(this, options);
    } else if (typeof plugin === 'object') {
      Object.keys(plugin).forEach(key => {
        this[key] = plugin[key];
      });
    }
    
    return this;
  }
  
  // 注册钩子
  hook(name, callback) {
    if (!this.hooks.has(name)) {
      this.hooks.set(name, []);
    }
    
    this.hooks.get(name).push(callback);
    
    // 返回取消注册函数
    return () => {
      const callbacks = this.hooks.get(name);
      if (callbacks) {
        const index = callbacks.indexOf(callback);
        if (index > -1) {
          callbacks.splice(index, 1);
        }
      }
    };
  }
  
  // 触发钩子
  triggerHook(name, ...args) {
    const callbacks = this.hooks.get(name);
    
    if (callbacks) {
      return Promise.all(
        callbacks.map(callback => callback(...args))
      );
    }
    
    return Promise.resolve();
  }
  
  // 添加中间件
  middleware(middleware) {
    this.middlewares.push(middleware);
    return this;
  }
  
  // 执行中间件链
  async runMiddlewares(context, next) {
    let index = -1;
    
    const dispatch = async (i) => {
      if (i <= index) {
        throw new Error('next() called multiple times');
      }
      
      index = i;
      let fn = this.middlewares[i];
      
      if (i === this.middlewares.length) {
        fn = next;
      }
      
      if (!fn) {
        return;
      }
      
      try {
        return await fn(context, () => dispatch(i + 1));
      } catch (error) {
        throw error;
      }
    };
    
    return dispatch(0);
  }
  
  // 事件总线
  on(event, callback) {
    return this.hook(`event:${event}`, callback);
  }
  
  emit(event, ...args) {
    return this.triggerHook(`event:${event}`, ...args);
  }
  
  // 生命周期管理
  async initialize() {
    await this.triggerHook('beforeInitialize');
    
    // 初始化所有插件
    for (const [name, plugin] of this.plugins) {
      if (plugin.initialize) {
        await plugin.initialize(this);
      }
    }
    
    await this.triggerHook('afterInitialize');
  }
  
  async destroy() {
    await this.triggerHook('beforeDestroy');
    
    // 销毁所有插件
    for (const [name, plugin] of this.plugins) {
      if (plugin.destroy) {
        await plugin.destroy(this);
      }
    }
    
    await this.triggerHook('afterDestroy');
  }
}

// 示例插件:日志插件
class LoggerPlugin {
  static install(app, options) {
    const logger = {
      info: (...args) => console.log('[INFO]', ...args),
      warn: (...args) => console.warn('[WARN]', ...args),
      error: (...args) => console.error('[ERROR]', ...args),
      debug: (...args) => console.debug('[DEBUG]', ...args)
    };
    
    // 添加到应用实例
    app.logger = logger;
    
    // 添加日志中间件
    app.middleware(async (ctx, next) => {
      const start = Date.now();
      logger.info(`Started ${ctx.method} ${ctx.url}`);
      
      try {
        await next();
        const duration = Date.now() - start;
        logger.info(`Completed ${ctx.status} in ${duration}ms`);
      } catch (error) {
        const duration = Date.now() - start;
        logger.error(`Failed in ${duration}ms:`, error);
        throw error;
      }
    });
    
    // 注册为插件
    app.plugins.set('logger', logger);
  }
}

// 示例插件:状态持久化插件
class PersistencePlugin {
  static install(app, options) {
    const { key = 'app-state', storage = localStorage } = options;
    
    // 保存状态
    app.saveState = function() {
      try {
        const state = this.store ? this.store.getState() : this.state;
        storage.setItem(key, JSON.stringify(state));
        app.logger?.info('State saved to storage');
      } catch (error) {
        app.logger?.error('Failed to save state:', error);
      }
    };
    
    // 加载状态
    app.loadState = function() {
      try {
        const saved = storage.getItem(key);
        if (saved) {
          const state = JSON.parse(saved);
          
          if (this.store) {
            this.store.dispatch({ type: '@@LOAD_SAVED_STATE', payload: state });
          } else {
            this.state = { ...this.state, ...state };
          }
          
          app.logger?.info('State loaded from storage');
          return state;
        }
      } catch (error) {
        app.logger?.error('Failed to load state:', error);
      }
      
      return null;
    };
    
    // 清除保存的状态
    app.clearState = function() {
      storage.removeItem(key);
      app.logger?.info('State cleared from storage');
    };
    
    // 自动保存钩子
    if (app.store) {
      app.store.subscribe(() => {
        app.saveState();
      });
    }
    
    // 注册生命周期钩子
    app.hook('afterInitialize', () => {
      app.loadState();
    });
  }
}

// 创建应用实例
class Application extends PluginSystem {
  constructor(config = {}) {
    super();
    
    this.config = config;
    this.components = new Map();
    this.directives = new Map();
    this.mixins = [];
    this.services = new Map();
    
    // 初始化核心服务
    this.initCoreServices();
  }
  
  initCoreServices() {
    // 事件总线
    this.services.set('events', new EventEmitter());
    
    // HTTP客户端
    this.services.set('http', new HttpClient({
      baseURL: this.config.apiBaseURL
    }));
    
    // 路由
    this.services.set('router', new HistoryRouter(
      this.config.routes || [],
      { base: this.config.baseURL }
    ));
    
    // 状态管理
    if (this.config.store) {
      this.services.set('store', this.config.store);
    }
  }
  
  // 组件注册
  component(name, component) {
    this.components.set(name, component);
    return this;
  }
  
  // 指令注册
  directive(name, directive) {
    this.directives.set(name, directive);
    return this;
  }
  
  // 混入
  mixin(mixin) {
    this.mixins.push(mixin);
    return this;
  }
  
  // 服务提供
  provide(name, service) {
    this.services.set(name, service);
    return this;
  }
  
  // 服务注入
  inject(name) {
    return this.services.get(name);
  }
  
  // 启动应用
  async mount(root) {
    await this.triggerHook('beforeMount');
    
    // 初始化插件
    await this.initialize();
    
    // 设置全局变量
    window.__app__ = this;
    
    // 挂载根组件
    if (this.config.rootComponent) {
      const RootComponent = this.config.rootComponent;
      const rootElement = typeof root === 'string' 
        ? document.querySelector(root) 
        : root;
      
      if (rootElement) {
        this.root = new RootComponent({
          app: this,
          router: this.services.get('router'),
          store: this.services.get('store')
        });
        
        this.root.$mount(rootElement);
      }
    }
    
    await this.triggerHook('afterMount');
    
    return this;
  }
  
  // 卸载应用
  async unmount() {
    await this.triggerHook('beforeUnmount');
    
    if (this.root && this.root.$destroy) {
      this.root.$destroy();
    }
    
    await this.destroy();
    await this.triggerHook('afterUnmount');
  }
}

// 事件发射器
class EventEmitter {
  constructor() {
    this.events = new Map();
  }
  
  on(event, listener) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event).push(listener);
    return () => this.off(event, listener);
  }
  
  off(event, listener) {
    if (!this.events.has(event)) return;
    
    const listeners = this.events.get(event);
    const index = listeners.indexOf(listener);
    
    if (index > -1) {
      listeners.splice(index, 1);
    }
    
    if (listeners.length === 0) {
      this.events.delete(event);
    }
  }
  
  emit(event, ...args) {
    if (!this.events.has(event)) return;
    
    const listeners = this.events.get(event).slice();
    
    for (const listener of listeners) {
      try {
        listener.apply(this, args);
      } catch (error) {
        console.error(`Error in event listener for ${event}:`, error);
      }
    }
  }
  
  once(event, listener) {
    const onceListener = (...args) => {
      this.off(event, onceListener);
      listener.apply(this, args);
    };
    
    return this.on(event, onceListener);
  }
}

// 使用完整的应用框架
const app = new Application({
  apiBaseURL: 'https://api.example.com',
  baseURL: '/app',
  
  routes: [
    { path: '/', component: HomePage },
    { path: '/about', component: AboutPage },
    { path: '/users/:id', component: UserPage }
  ],
  
  rootComponent: AppRoot
});

// 使用插件
app
  .use(LoggerPlugin)
  .use(PersistencePlugin, { key: 'my-app-state' })
  .use({
    install(app) {
      // 自定义插件
      app.logger?.info('Custom plugin installed');
    }
  });

// 注册全局组件
app.component('button-counter', {
  data() {
    return { count: 0 };
  },
  
  template: `
    <button @click="count++">
      Clicked {{ count }} times
    </button>
  `
});

// 注册全局指令
app.directive('focus', {
  inserted(el) {
    el.focus();
  }
});

// 添加全局混入
app.mixin({
  created() {
    console.log('Component created');
  }
});

// 启动应用
app.mount('#app').then(() => {
  console.log('Application mounted');
});

七、构建工具与打包

javascript 复制代码
// 简单的模块打包器
class SimpleBundler {
  constructor(entry) {
    this.entry = entry;
    this.modules = new Map();
    this.bundle = null;
  }
  
  // 构建
  async build() {
    await this.collectModules(this.entry);
    this.bundle = this.generateBundle();
    return this.bundle;
  }
  
  // 收集模块
  async collectModules(filePath, visited = new Set()) {
    if (visited.has(filePath)) return;
    visited.add(filePath);
    
    // 读取文件内容
    const content = await this.readFile(filePath);
    
    // 解析依赖
    const deps = this.parseDependencies(content);
    const code = this.transformCode(content);
    
    this.modules.set(filePath, {
      code,
      deps
    });
    
    // 递归收集依赖
    for (const dep of deps) {
      const depPath = this.resolvePath(filePath, dep);
      await this.collectModules(depPath, visited);
    }
  }
  
  // 生成打包代码
  generateBundle() {
    const modules = {};
    
    this.modules.forEach((module, id) => {
      modules[id] = {
        code: module.code,
        deps: module.deps
      };
    });
    
    return `
      (function(modules) {
        function require(id) {
          const { code, deps } = modules[id];
          
          function localRequire(relativePath) {
            return require(deps[relativePath]);
          }
          
          const module = { exports: {} };
          const fn = new Function('require', 'module', 'exports', code);
          fn(localRequire, module, module.exports);
          
          return module.exports;
        }
        
        require('${this.entry}');
      })({
        ${Array.from(this.modules.entries()).map(([id, module]) => {
          return `'${id}': {
            code: ${JSON.stringify(module.code)},
            deps: ${JSON.stringify(module.deps)}
          }`;
        }).join(',\n')}
      });
    `;
  }
  
  // 解析依赖
  parseDependencies(content) {
    const deps = new Set();
    
    // 匹配import语句
    const importRegex = /import\s+(?:.*?\s+from\s+)?['"](.+?)['"]/g;
    let match;
    
    while ((match = importRegex.exec(content)) !== null) {
      deps.add(match[1]);
    }
    
    // 匹配require调用
    const requireRegex = /require\(['"](.+?)['"]\)/g;
    
    while ((match = requireRegex.exec(content)) !== null) {
      deps.add(match[1]);
    }
    
    return Array.from(deps);
  }
  
  // 转换代码
  transformCode(content) {
    // 简单转换:移除import/export语句
    let transformed = content
      .replace(/import\s+.*?\s+from\s+['"](.+?)['"]/g, '')
      .replace(/export\s+(?:default\s+)?/g, '');
    
    return transformed;
  }
  
  // 读取文件
  async readFile(path) {
    // 模拟文件读取
    const response = await fetch(path);
    return response.text();
  }
  
  // 解析路径
  resolvePath(base, relative) {
    const baseDir = base.substring(0, base.lastIndexOf('/'));
    
    if (relative.startsWith('./')) {
      return baseDir + '/' + relative.slice(2);
    }
    
    if (relative.startsWith('../')) {
      let current = baseDir;
      let target = relative;
      
      while (target.startsWith('../')) {
        current = current.substring(0, current.lastIndexOf('/'));
        target = target.slice(3);
      }
      
      return current + '/' + target;
    }
    
    return relative;
  }
}

// 使用打包器
const bundler = new SimpleBundler('./src/index.js');
bundler.build().then(bundle => {
  console.log('Bundle generated:', bundle.length, 'bytes');
  
  // 执行打包后的代码
  eval(bundle);
});

// 开发服务器
class DevServer {
  constructor(config) {
    this.config = config;
    this.middlewares = [];
    this.watchers = new Map();
  }
  
  // 添加中间件
  use(middleware) {
    this.middlewares.push(middleware);
    return this;
  }
  
  // 启动服务器
  async start() {
    console.log(`Starting dev server on http://localhost:${this.config.port}`);
    
    // 热重载中间件
    this.use(this.hotReloadMiddleware());
    
    // 文件服务中间件
    this.use(this.staticMiddleware());
    
    // 编译中间件
    this.use(this.compileMiddleware());
    
    // 启动文件监听
    this.startWatching();
    
    // 启动HTTP服务器
    await this.startHTTPServer();
  }
  
  // 热重载中间件
  hotReloadMiddleware() {
    return async (ctx, next) => {
      await next();
      
      // 如果是HTML文件,注入热重载脚本
      if (ctx.path.endsWith('.html')) {
        ctx.body = ctx.body.replace(
          '</body>',
          '<script src="/hot-reload.js"></script></body>'
        );
      }
    };
  }
  
  // 静态文件服务
  staticMiddleware() {
    return async (ctx, next) => {
      // 处理静态文件请求
      if (ctx.method === 'GET' && ctx.path.startsWith('/static/')) {
        const filePath = ctx.path.slice(7);
        const content = await this.readFile(filePath);
        
        ctx.body = content;
        ctx.type = this.getMimeType(filePath);
        return;
      }
      
      await next();
    };
  }
  
  // 编译中间件
  compileMiddleware() {
    return async (ctx, next) => {
      // 处理JS文件请求
      if (ctx.path.endsWith('.js')) {
        const filePath = ctx.path.slice(1);
        const content = await this.readFile(filePath);
        
        // 简单编译
        const compiled = this.compileJS(content);
        
        ctx.body = compiled;
        ctx.type = 'application/javascript';
        return;
      }
      
      await next();
    };
  }
  
  // 编译JS
  compileJS(content) {
    // 简单Babel转译
    return content
      .replace(/const\s+/g, 'var ')
      .replace(/let\s+/g, 'var ')
      .replace(/export\s+/g, '// export ')
      .replace(/import\s+/g, '// import ');
  }
  
  // 读取文件
  async readFile(path) {
    // 模拟文件读取
    return `// Content of ${path}`;
  }
  
  // 获取MIME类型
  getMimeType(path) {
    const types = {
      '.js': 'application/javascript',
      '.css': 'text/css',
      '.html': 'text/html',
      '.json': 'application/json'
    };
    
    const ext = path.substring(path.lastIndexOf('.'));
    return types[ext] || 'text/plain';
  }
  
  // 启动文件监听
  startWatching() {
    console.log('Watching for file changes...');
    
    // 模拟文件监听
    setInterval(() => {
      // 检查文件变化
      this.checkFileChanges();
    }, 1000);
  }
  
  // 检查文件变化
  checkFileChanges() {
    // 模拟文件变化检测
    const changedFiles = [];
    
    if (changedFiles.length > 0) {
      console.log('Files changed:', changedFiles);
      this.emit('fileChange', changedFiles);
    }
  }
  
  // 启动HTTP服务器
  async startHTTPServer() {
    console.log('HTTP server started');
    
    // 模拟HTTP请求处理
    return new Promise((resolve) => {
      // 服务器运行中...
      setTimeout(() => {
        console.log('Server stopped');
        resolve();
      }, 3600000); // 1小时后停止
    });
  }
  
  // 事件发射
  emit(event, data) {
    if (this.listeners && this.listeners[event]) {
      this.listeners[event].forEach(listener => listener(data));
    }
  }
  
  on(event, listener) {
    if (!this.listeners) this.listeners = {};
    if (!this.listeners[event]) this.listeners[event] = [];
    this.listeners[event].push(listener);
  }
}

// 启动开发服务器
const devServer = new DevServer({
  port: 3000,
  watch: ['src/**/*.js', 'src/**/*.css']
});

devServer.on('fileChange', (files) => {
  console.log('Hot reloading...', files);
});

devServer.start().catch(console.error);

总结

通过亲手实现这些核心功能,我们不仅深入理解了现代前端框架的设计原理,还掌握了构建可维护、可扩展的库和工具的能力。从响应式系统到状态管理,从路由到表单验证,每个模块都是构建复杂应用的基础。

关键收获:

  1. MVVM框架: 理解了数据绑定、虚拟DOM和组件化的核心原理
  2. 状态管理: 掌握了单向数据流和中间件机制的设计思想
  3. 路由系统: 实现了客户端路由的核心功能,包括嵌套路由和守卫
  4. 表单处理: 构建了完整的验证和表单状态管理系统
  5. HTTP客户端: 实现了拦截器、取消请求等高级特性
  6. 插件系统: 设计了可扩展的应用程序架构

这些实现虽然简化,但涵盖了核心概念。在实际项目中,我们可以基于这些理解,更好地使用和定制现有框架,甚至构建符合特定需求的工具库。

记住,框架和库的本质是解决问题的方法论。理解其设计思想比记住API更为重要。通过不断实践和重构,你将能够设计出更优雅、更高效的解决方案。

相关推荐
文心快码BaiduComate2 小时前
Spec模式赋能百度网盘场景提效
前端·程序员·前端框架
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之find命令(实操篇)
linux·运维·前端·chrome·笔记
一念之间lq2 小时前
Elpis 第四阶段· Vue3 完成动态组件建设
前端·vue.js
akira09122 小时前
滚动控制视频播放是如何实现的?GSAP ScrollTrigger + seek 实践 vivo官网案例
前端·产品
用户636836608552 小时前
前端使用nuxt.js的seo优化
前端
OldBirds2 小时前
烧脑时刻:Dart 中异步生成器与流
前端·后端
湛海不过深蓝2 小时前
【echarts】折线图颜色分段设置不同颜色
前端·javascript·echarts
昨晚我输给了一辆AE862 小时前
关于 react-hook-form 的 isValid 在有些场景下的值总是 false 问题
前端·react.js
xinyu_Jina2 小时前
Calculator Game:WebAssembly在计算密集型组合优化中的性能优势
前端·ui·性能优化