• 浅析 Vue3 响应式原理


    Proxy

    Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作。

    1. const obj = { a1 };
    2. const p = new Proxy(obj, {
    3.   get(target, property, receiver) {
    4.     console.log("get");
    5.     return Reflect.get(target, property, receiver);
    6.   },
    7.   set(target, property, value, receiver) {
    8.     console.log("set");
    9.     return Reflect.set(target, property, receiver);
    10.   },
    11.   has(target, prop) {
    12.     console.log("has");
    13.     return Reflect.has(target, prop);
    14.   },
    15.   deleteProperty(target, prop) {
    16.     console.log("deleteProperty");
    17.     return Reflect.deleteProperty(target, prop);
    18.   },
    19. });
    20. p.a// 输出 --> get
    21. p.a = 2// 输出 --> set
    22. "a" in p; // 输出 --> has
    23. delete p.a// 输出 --> deleteProperty

    如上例子,我们用 Proxy 代理了 Obj 对象的属性访问、属性赋值、in 操作符、delete 的操作,并进行 console.log 输出。

    Reflect

    Reflect 是与 Proxy 搭配使用的一个 API,当我们劫持了某些操作时,如果需要再把这些操作反射回去,那么就需要 Reflect 这个 API。

    由于我们拦截了对象的操作,所以这些操作该有的功能都丧失了,例如,访问属性 p.a 应该得到 a 属性的值,但此时却不会有任何结果,如果我们还想拥有拦截之前的功能,那我们就需要用 Reflect 反射回去。

    1. const obj = { a1 };
    2. const p = new Proxy(obj, {
    3.   get(target, property, receiver) {
    4.     console.log("get");
    5.     return Reflect.get(target, property, receiver);
    6.   },
    7.   set(target, property, value, receiver) {
    8.     console.log("set");
    9.     return Reflect.set(target, property, receiver);
    10.   },
    11.   has(target, prop) {
    12.     console.log("has");
    13.     return Reflect.has(target, prop);
    14.   },
    15.   deleteProperty(target, prop) {
    16.     console.log("deleteProperty");
    17.     return Reflect.deleteProperty(target, prop);
    18.   },
    19. });

    举个例子

    以下全文我们都会通过这个例子来讲述 Vue3 响应式的原理。

    1. <div id="app">div>
    2. <script>
    3.   // 创建一个响应式对象
    4.   const state = reactive({ counter1 });
    5.   // 立即运行一个函数,当响应式对象的属性发生改变时重新执行。
    6.   effect(() => {
    7.     document.querySelector("#app").innerHTML = state.counter;
    8.   });
    9.   // 2s 后视图更新
    10.   setTimeout(() => {
    11.     state.counter += 1;
    12.   }, 2000);
    13. script>

    我们用 reactive 创建了一个响应式对象 state,并调用了 effect 方法,该方法接受一个副作用函数,effect 的执行会立即调用副作用函数,并将 state.counter 赋值给 #app.innerHTML;两秒后,state.counter += 1,此时,effect 的副作用函数会重新执行,页面也会变成 2.

    内部的执行过程大概如下图所示:

    1. 调用 reactive() 返回一个 Proxy 代理对象,并劫持对象的 get 与 set 操作

    2. 调用 effect() 方法时,会访问属性 state.counter,此时会触发 proxy 的 get 操作。

    3. get 方法会调用 track() 进行依赖收集;建立一个对象(state)、属性(counter)、effect 副作用函数的依赖关系;

    4. set 方法会调用 trigger() 进行依赖更新;通过对象(state)与属性(coutner)找到对应的 effect 副作用函数,然后重新执行。

    reactive

    reactive 会返回如下一个 Proxy 对象

    1. const reactive = (target) => {
    2.   return new Proxy(target, {
    3.     get(target, key, receiver) {
    4.       const res = Reflect.get(target, key, receiver);
    5.       track(target, key); // 收集依赖
    6.       if (isObject(res)) {
    7.         // 如果当前获取的属性值是一个对象,则继续将为此对象创建 Proxy 代理
    8.         return reactive(res);
    9.       }
    10.       return res;
    11.     },
    12.     set(target, key, value, receiver) {
    13.       Reflect.set(target, key, value, receiver);
    14.       trigger(target, key); // 依赖更新
    15.     },
    16.   });
    17. };

    effect

    1. let activeEffect;
    2. function effect(fn{
    3.   const _effect = function reactiveEffect({
    4.     activeEffect = _effect;
    5.     fn();
    6.   };
    7.   _effect();
    8. }

    首先定义全局的 activeEffect,它永远指向当前正在执行的 effect 副作用函数。effect 为 fn 创建一个内部的副作用函数,然后立即执行,此时会触发对象的 get 操作,调用 track() 方法。

    1. effect(() => {
    2.   // effect 的立即执行会访问 state.counter,触发了对象的 get 操作。
    3.   document.querySelector("#app").innerHTML = state.counter;
    4. });

    track

    track 会建立一个 对象(state) => 属性(counter) => effect 的一个依赖关系

    1. const targetMap = new WeakMap();
    2. function track(target, key) {
    3.   if (!activeEffect) {
    4.     return;
    5.   }
    6.   let depsMap = targetMap.get(target);
    7.   if (!depsMap) {
    8.     targetMap.set(target, (depsMap = new Map()));
    9.   }
    10.   let dep = depsMap.get(key);
    11.   if (!dep) {
    12.     depsMap.set(key, (dep = new Set()));
    13.   }
    14.   if (!dep.has(activeEffect)) {
    15.     dep.add(activeEffect);
    16.   }
    17. }

    执行完成成后我们得到一个如下的数据结构:

    1. // map 集合
    2.   {
    3.     key: {counter1// state 对象,
    4.     value: [ // map 集合
    5.       {
    6.         key"counter",
    7.         value: [ // set
    8.           function reactiveEffect() {} // effect 副作用函数
    9.         ],
    10.       }
    11.     ],
    12.   },
    13. ];

     

    注意:当我们调用 effect 时,会将当前的副作用函数赋值给全局的 activeEffect,所以此时我们可以正确关联其依赖。

    trigger

    当我们给 state.counter 赋值的时候就会触发代理对象的 set 操作,从而调用 trigger 方法

    1. setTimeout(() => {
    2.   // 给 counter 属性赋值会触发 set 操作
    3.   state.counter += 1;
    4. }, 2000);
    1. function trigger(target, key) {
    2.   const depsMap = targetMap.get(target);
    3.   if (!depsMap) return;
    4.   const effects = depsMap.get(key);
    5.   effects && effects.forEach((effect) => effect());
    6. }

  • 相关阅读:
    Mysql优化---锁机制、行锁及表锁
    使用vagrant安装CentOS7虚拟机
    各种排序算法
    java 通过 冰蓝 word 转pdf ,最大程度包装pdf 样式和word接近
    磷酸铁锂电池回收浸出液除铝
    X2Keyarch迁移工具实战 | 将CentOS高效迁移至浪潮云峦操作系统KeyarchOS
    C++基础知识(九)--- 类型转换 & 异常
    成功实施持续测试的 3 个关键
    C++ 学习(六)函数 及 函数的分文件编写
    C++友元函数
  • 原文地址:https://blog.csdn.net/qq_41581588/article/details/126279594