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

7.8 KiB
Raw Permalink Blame History

框架相关算法

1. 虚拟DOM Diff 算法

场景React/Vue 核心渲染机制、理解为何需要 key 属性、性能优化方向。 解决:直接操作真实 DOM 性能开销大,通过对比新旧虚拟树最小化 DOM 操作。

简化版 Diff 实现

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)

// 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)

// 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 为何不能在条件语句中调用。 解决:函数组件无状态,通过闭包和数组模拟类组件的状态能力。

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 等。

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

场景:跨组件状态管理、理解单向数据流架构、实现时间旅行调试。 解决:多组件共享状态混乱,集中管理应用状态并可预测地变更。

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 返回原状态
  }
};