webDevInterviewQuestions/algorithm/05-framework-algorithms.md
2026-01-19 10:21:18 +08:00

281 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 框架相关算法
## 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 返回原状态
}
};
```