引言
在当今的前端开发中,理解框架和库的内部原理比单纯使用它们更为重要。通过亲手实现核心功能,我们不仅能深入理解其设计思想,还能在面对复杂问题时提出更优的解决方案。本文将带你从零开始,完整实现一个小型但功能齐全的前端框架/库,涵盖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);
总结
通过亲手实现这些核心功能,我们不仅深入理解了现代前端框架的设计原理,还掌握了构建可维护、可扩展的库和工具的能力。从响应式系统到状态管理,从路由到表单验证,每个模块都是构建复杂应用的基础。
关键收获:
- MVVM框架: 理解了数据绑定、虚拟DOM和组件化的核心原理
- 状态管理: 掌握了单向数据流和中间件机制的设计思想
- 路由系统: 实现了客户端路由的核心功能,包括嵌套路由和守卫
- 表单处理: 构建了完整的验证和表单状态管理系统
- HTTP客户端: 实现了拦截器、取消请求等高级特性
- 插件系统: 设计了可扩展的应用程序架构
这些实现虽然简化,但涵盖了核心概念。在实际项目中,我们可以基于这些理解,更好地使用和定制现有框架,甚至构建符合特定需求的工具库。
记住,框架和库的本质是解决问题的方法论。理解其设计思想比记住API更为重要。通过不断实践和重构,你将能够设计出更优雅、更高效的解决方案。