vue3底层原理解析

proxy代理实现响应式的底层原理

1.proxy代理:target为被代理的值,handler中的get和set函数是被代理对象被访问和修改时触发的操作,用proxy实现的原理即为用proxy代理要实现响应式的值,然后再proxy的handler中的set收集依赖于这个值更新而触发的副作用函数,然后set再执行这些收集的副作用函数

js 复制代码
const proxyMap = new WeakMap();

const handler = {
  // 拦截获取属性操作
  get(target, prop, receiver) {
    const value = target[prop];
    
    // 如果值是对象类型且非 null,递归地为该对象创建 Proxy(深度代理)
    if (typeof value === 'object' && value !== null) {
      if (!proxyMap.has(value)) {
        // 缓存已代理的对象,避免重复代理
        proxyMap.set(value, new Proxy(value, handler)); //如果对象的属性还是一个对象,
                                                        //设置值为一个新的proxy对象,实现对象的深层拦截
      }
      return proxyMap.get(value); // 返回已代理的对象
    }
    
    return value; // 如果不是对象类型,则直接返回值
  },
  
  // 拦截设置属性操作
  set(target, prop, value, receiver) {
    console.log(`Setting ${prop} to ${value}`);
    target[prop] = value;
    return true; // 返回 true 表示设置成功
  },
}

2.vue3利用proxy实现响应式

js 复制代码
   // targetMap 用来存储响应式对象和它们的属性依赖关系
const targetMap = new WeakMap();

// 用来存储当前正在执行的副作用函数
let activeEffect = null;

function effect(fn) {
  activeEffect = fn;  // 设置当前的副作用函数
  fn();               // 执行副作用函数
  activeEffect = null; // 执行完后清除副作用函数
}

function track(target, key) {
  if (!activeEffect) return;  // 如果没有副作用函数,就不收集依赖

  // 从 targetMap 中获取 target 对象的 depsMap
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()));  // 初始化 depsMap
  }

  // 从 depsMap 中获取该属性的依赖集合
  let deps = depsMap.get(key);
  if (!deps) {
    depsMap.set(key, (deps = new Set()));  // 初始化 deps 集合
  }

  // 将当前的副作用函数添加到依赖集合中
  deps.add(activeEffect);
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;

  const deps = depsMap.get(key);
  if (!deps) return;

  // 遍历依赖集合并执行每个副作用函数
  deps.forEach(effect => effect());
}

function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      track(target, key);  // 收集依赖
      return target[key];  // 返回属性值
    },
    set(target, key, value) {
      target[key] = value;  // 修改属性值
      trigger(target, key);  // 触发更新
      return true;
    }
  });
}

// 示例

const state = reactive({ count: 0 });

effect(() => {
  console.log(`count is: ${state.count}`);
});

state.count = 1;  // 修改 count 会触发 `trigger`,执行副作用函数

相关知识:

  1. proxy代理的是整个对象,可以拦截整个对象的操作(如果对象的属性还是对象,那么递归的为这个属性创造一个新的proxy对象),不像vue2用object.defineproperty需要给每个对象的属性设置getter和setter,而且proxy支持动态拦截,可以拦截到对象上的新属性,而且支持

watch

js 复制代码
function watch(source, cb) {
  let getter;
  if (typeof source === 'function') {
    getter = source;  // 如果 source 是一个函数,则直接用它作为 getter
  } else {
    getter = () => traverse(source);  // 如果 source 是一个对象,则对其进行递归遍历
  }

  let oldValue, newValue;

  // 定义 effectFn,封装副作用函数
  const effectFn = effect(
    () => getter(),  // 获取需要监听的数据
    {
      lazy: true,  // 初始化时不执行 effect
      scheduler() {
        // 当数据变化时触发的调度器
        newValue = effectFn();  // 获取新的值
        cb(newValue, oldValue);  // 执行回调,传入新旧值
        oldValue = newValue;  // 更新旧值
      }
    }
  );

  // 初次执行 effectFn,得到初始值
  oldValue = effectFn();
}

computed

js 复制代码
function computed(getter) {
  let value;
  let dirty = true;

  // effectFn 是由 effect 创建的副作用函数
  const effectFn = effect(getter, {
    lazy: true,  // 延迟执行,只有在计算属性被访问时才计算
    scheduler() {
      dirty = true;  // 当依赖的值发生变化时,标记为脏数据,表示需要重新计算
    }
  });

  return {
    get value() {
      if (dirty) {
        value = effectFn();  // 重新计算
        dirty = false;        // 计算完后标记为不脏
      }
      return value;           // 返回缓存值
    }
  };
}
相关推荐
web守墓人4 小时前
【gpt生成-其一】以go语言为例,详细描述一下 :语法规范BNF/EBNF形式化描述
前端·gpt·golang
pink大呲花6 小时前
使用 Axios 进行 API 请求与接口封装:打造高效稳定的前端数据交互
前端·vue.js·交互
samuel9186 小时前
uniapp通过uni.addInterceptor实现路由拦截
前端·javascript·uni-app
泯泷7 小时前
JavaScript随机数生成技术实践 | 为什么Math.random不是安全的随机算法?
前端·javascript·安全
benben0447 小时前
Unity3D仿星露谷物语开发35之锄地动画
前端·游戏·游戏引擎
WebInfra7 小时前
🔥 Midscene 重磅更新:支持 AI 驱动的 Android 自动化
android·前端·测试
八了个戒7 小时前
「数据可视化 D3系列」入门第八章:动画效果详解(让图表动起来)
开发语言·前端·javascript·数据可视化
拉不动的猪8 小时前
无缝适配 PC 和移动端‌我们要注意哪些点呢
前端·javascript·面试
酱酱们的每日掘金9 小时前
🔥 4 月精选:AICoding Cursor上新与 MCP 实战揭秘!- AI Coding 周刊第 5 期
前端·ai编程·mcp
天天扭码9 小时前
一分钟解决 | 高频面试算法题——和为 K 的子数组(前缀和)
前端·算法·面试