281 lines
7.8 KiB
Markdown
281 lines
7.8 KiB
Markdown
# 框架相关算法
|
||
|
||
## 1. 虚拟DOM Diff 算法
|
||
|
||
> **场景**:React/Vue 核心渲染机制、理解为何需要 key 属性、性能优化方向。
|
||
> **解决**:直接操作真实 DOM 性能开销大,通过对比新旧虚拟树最小化 DOM 操作。
|
||
|
||
### 简化版 Diff 实现
|
||
|
||
```js
|
||
function diff(oldVNode, newVNode) {
|
||
const patches = []; // 存储所有差异
|
||
|
||
// 新节点不存在,删除老节点
|
||
if (!newVNode) {
|
||
patches.push({ type: 'REMOVE' });
|
||
return patches;
|
||
}
|
||
|
||
// 类型不同或文本不同或标签不同,直接替换
|
||
if (typeof oldVNode !== typeof newVNode ||
|
||
(typeof oldVNode === 'string' && oldVNode !== newVNode) ||
|
||
oldVNode.tag !== newVNode.tag) {
|
||
patches.push({ type: 'REPLACE', node: newVNode });
|
||
return patches;
|
||
}
|
||
|
||
// 比较属性差异
|
||
if (newVNode.props) {
|
||
const propsPatches = diffProps(oldVNode.props || {}, newVNode.props);
|
||
if (Object.keys(propsPatches).length) {
|
||
patches.push({ type: 'PROPS', props: propsPatches });
|
||
}
|
||
}
|
||
|
||
// 递归比较子节点
|
||
diffChildren(oldVNode.children || [], newVNode.children || [], patches);
|
||
|
||
return patches;
|
||
}
|
||
|
||
// 属性对比
|
||
function diffProps(oldProps, newProps) {
|
||
const patches = {};
|
||
// 检查新增或修改的属性
|
||
for (const key in newProps) {
|
||
if (newProps[key] !== oldProps[key]) {
|
||
patches[key] = newProps[key];
|
||
}
|
||
}
|
||
// 检查删除的属性
|
||
for (const key in oldProps) {
|
||
if (!(key in newProps)) {
|
||
patches[key] = undefined; // 标记为删除
|
||
}
|
||
}
|
||
return patches;
|
||
}
|
||
|
||
// 子节点对比
|
||
function diffChildren(oldChildren, newChildren, patches) {
|
||
const len = Math.max(oldChildren.length, newChildren.length);
|
||
for (let i = 0; i < len; i++) {
|
||
// 递归对比每个子节点
|
||
const childPatches = diff(oldChildren[i], newChildren[i]);
|
||
if (childPatches.length) {
|
||
patches.push({ type: 'CHILDREN', index: i, patches: childPatches });
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Vue 响应式原理
|
||
|
||
> **场景**:Vue 项目开发、理解数据驱动视图更新、排查响应式失效问题。
|
||
> **解决**:手动操作 DOM 繁琐易错,自动追踪数据变化并更新视图。
|
||
|
||
### Vue 2 (Object.defineProperty)
|
||
|
||
```js
|
||
// Vue 2 使用 Object.defineProperty 劫持 getter/setter
|
||
function observe(obj) {
|
||
if (typeof obj !== 'object' || obj === null) return;
|
||
|
||
Object.keys(obj).forEach(key => {
|
||
let value = obj[key];
|
||
const dep = new Set(); // 依赖收集器,存储依赖该属性的效果函数
|
||
|
||
observe(value); // 递归处理嵌套对象
|
||
|
||
Object.defineProperty(obj, key, {
|
||
get() {
|
||
// 读取时收集依赖
|
||
if (currentEffect) dep.add(currentEffect);
|
||
return value;
|
||
},
|
||
set(newVal) {
|
||
if (newVal === value) return; // 值未变化则不触发
|
||
value = newVal;
|
||
observe(newVal); // 新值也要响应式化
|
||
dep.forEach(fn => fn()); // 触发所有依赖更新
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
let currentEffect = null;
|
||
// 注册副作用函数,自动追踪依赖
|
||
function watchEffect(fn) {
|
||
currentEffect = fn; // 设置当前执行的效果函数
|
||
fn(); // 执行时触发 getter,完成依赖收集
|
||
currentEffect = null;
|
||
}
|
||
```
|
||
|
||
### Vue 3 (Proxy)
|
||
|
||
```js
|
||
// Vue 3 使用 Proxy 代理对象
|
||
function reactive(obj) {
|
||
return new Proxy(obj, {
|
||
get(target, key, receiver) {
|
||
track(target, key); // 收集依赖
|
||
const result = Reflect.get(target, key, receiver);
|
||
// 嵌套对象也要转为响应式
|
||
return typeof result === 'object' ? reactive(result) : result;
|
||
},
|
||
set(target, key, value, receiver) {
|
||
const result = Reflect.set(target, key, value, receiver);
|
||
trigger(target, key); // 触发更新
|
||
return result;
|
||
}
|
||
});
|
||
}
|
||
|
||
// 依赖存储结构:WeakMap<target, Map<key, Set<effect>>>
|
||
const targetMap = new WeakMap();
|
||
let activeEffect = null;
|
||
|
||
// 收集依赖
|
||
function track(target, key) {
|
||
if (!activeEffect) return;
|
||
let depsMap = targetMap.get(target);
|
||
if (!depsMap) targetMap.set(target, (depsMap = new Map()));
|
||
let dep = depsMap.get(key);
|
||
if (!dep) depsMap.set(key, (dep = new Set()));
|
||
dep.add(activeEffect); // 将当前效果函数加入依赖集合
|
||
}
|
||
|
||
// 触发更新
|
||
function trigger(target, key) {
|
||
const depsMap = targetMap.get(target);
|
||
if (!depsMap) return;
|
||
depsMap.get(key)?.forEach(effect => effect()); // 执行所有依赖的效果函数
|
||
}
|
||
|
||
// 注册效果函数
|
||
function effect(fn) {
|
||
activeEffect = fn;
|
||
fn(); // 执行时触发 getter,完成依赖收集
|
||
activeEffect = null;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. React useState 简单模拟
|
||
|
||
> **场景**:React 函数组件状态管理、理解 Hooks 为何不能在条件语句中调用。
|
||
> **解决**:函数组件无状态,通过闭包和数组模拟类组件的状态能力。
|
||
|
||
```js
|
||
let state = []; // 全局状态数组
|
||
let stateIndex = 0; // 当前状态索引
|
||
|
||
function useState(initialValue) {
|
||
const currentIndex = stateIndex; // 闭包保存当前索引
|
||
// 首次调用时初始化
|
||
state[currentIndex] = state[currentIndex] ?? initialValue;
|
||
|
||
const setState = (newValue) => {
|
||
// 支持函数式更新:setState(prev => prev + 1)
|
||
state[currentIndex] = typeof newValue === 'function'
|
||
? newValue(state[currentIndex])
|
||
: newValue;
|
||
render(); // 触发重新渲染
|
||
};
|
||
|
||
stateIndex++; // 下一个 useState 使用下一个索引
|
||
return [state[currentIndex], setState];
|
||
}
|
||
|
||
// 渲染函数:重置索引,重新执行组件
|
||
function render() {
|
||
stateIndex = 0; // 重置索引,这就是为什么 Hooks 不能在条件语句中调用
|
||
// 调用组件函数...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. useEffect 简单模拟
|
||
|
||
> **场景**:React 副作用处理(数据请求、事件订阅、定时器)、理解依赖数组作用。
|
||
> **解决**:函数组件中处理生命周期和副作用,替代类组件的 componentDidMount 等。
|
||
|
||
```js
|
||
let effectIndex = 0; // 当前 effect 索引
|
||
let effects = []; // 存储所有 effect 信息
|
||
|
||
function useEffect(callback, deps) {
|
||
const currentIndex = effectIndex;
|
||
const prevDeps = effects[currentIndex]?.deps; // 上次的依赖数组
|
||
|
||
// 判断依赖是否变化
|
||
const hasChanged = !prevDeps ||
|
||
deps.some((dep, i) => !Object.is(dep, prevDeps[i]));
|
||
|
||
if (hasChanged) {
|
||
// 执行上次的清理函数
|
||
effects[currentIndex]?.cleanup?.();
|
||
|
||
// 异步执行 effect(模拟 React 的行为)
|
||
Promise.resolve().then(() => {
|
||
const cleanup = callback(); // 执行 effect,返回清理函数
|
||
effects[currentIndex] = { deps, cleanup };
|
||
});
|
||
}
|
||
|
||
effectIndex++;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 简易 Redux
|
||
|
||
> **场景**:跨组件状态管理、理解单向数据流架构、实现时间旅行调试。
|
||
> **解决**:多组件共享状态混乱,集中管理应用状态并可预测地变更。
|
||
|
||
```js
|
||
function createStore(reducer) {
|
||
let state; // 应用状态
|
||
const listeners = []; // 订阅的监听器列表
|
||
|
||
// 获取当前状态
|
||
const getState = () => state;
|
||
|
||
// 发起 action,更新状态
|
||
const dispatch = (action) => {
|
||
state = reducer(state, action); // 通过 reducer 计算新状态
|
||
listeners.forEach(listener => listener()); // 通知所有订阅者
|
||
};
|
||
|
||
// 订阅状态变化
|
||
const subscribe = (listener) => {
|
||
listeners.push(listener);
|
||
// 返回取消订阅函数
|
||
return () => {
|
||
const index = listeners.indexOf(listener);
|
||
listeners.splice(index, 1);
|
||
};
|
||
};
|
||
|
||
dispatch({ type: '@@INIT' }); // 初始化 state
|
||
|
||
return { getState, dispatch, subscribe };
|
||
}
|
||
|
||
// 使用示例:计数器 reducer
|
||
const reducer = (state = { count: 0 }, action) => {
|
||
switch (action.type) {
|
||
case 'INCREMENT': return { count: state.count + 1 };
|
||
case 'DECREMENT': return { count: state.count - 1 };
|
||
default: return state; // 未知 action 返回原状态
|
||
}
|
||
};
|
||
```
|