Vue3响应式原理 私

响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。

响应式的实现方式就是劫持数据,Vue3的reactive就是通过Proxy劫持数据,由于劫持的是整个对象,所以可以检测到任何对象的修改,弥补了2.0的不足。

名词解释:

  • **副作用函数:**函数的执行会直接或间接影响其他函数的执行,这时我们说函数产生了副作用。副作用很容易产生,例如一个函数修改了全局变量,这其实也是一个副作用。
  • targetmap 是一个 weakmap 类型的集合,用来存储副作用函数,从类型定义可以看出 targetmap的数据结构方式:

weakmap 由 target --> map 构成

map 由 key --> set 构成

  • 其中 weakmap 的键是原始对象 target,weakmap 的值是一个 map 实例,map 的键是原始对象 target 的 key,map 的值是一个由副作用函数组成的 set。
  • effect函数:创建一个副作用函数,接受两个参数,分别是用户自定义的fn函数和options 选项。
  • track收集依赖:访问数据的时候,触发get函数,get函数最核心的部分是执行track函数收集依赖。这其实是一种懒操作。
  • trigger派发更新:当对属性进行赋值时,会触发代理对象的 set 拦截函数执行。

track函数收集依赖的实现

javascript 复制代码
export function track(target: object, type: trackoptypes, key: unknown) {
    // 如果开启了依赖收集并且有正在执行的副作用,则收集依赖
  if (shouldtrack && activeeffect) {
    // 在 targetmap 中获取对应的 target 的依赖集合
    let depsmap = targetmap.get(target)
    if (!depsmap) {
      // 如果 target 不在 targetmap 中,则将当前 target 添加进 targetmap 中,并将 targetmap 的 value 初始化为 new map()。
      targetmap.set(target, (depsmap = new map()))
    }
    // 从依赖集合中获取对应的 key 的依赖
    let dep = depsmap.get(key)
    if (!dep) {
      // 如果 key 不存在,将这个 key 作为依赖收集起来,并将依赖初始化为 new set()
      depsmap.set(key, (dep = createdep()))
    }
  // 最后调用 trackeffects收集副作用函数,将副作用函数收集到依赖集合depsmap中。
    const eventinfo = __dev__
      ? { effect: activeeffect, target, type, key }
      : undefined
 
    trackeffects(dep, eventinfo)
  }
}

trackeffects 函数

收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。

javascript 复制代码
// 收集副作用函数,在 trackeffects 函数中,检查当前正在执行的副作用函数 activeeffect 是否已经被收集到依赖集合中,如果没有,就将当前的副作用函数收集到依赖集合中。同时在当前副作用函数的 deps 属性中记录该依赖。
export function trackeffects(
  dep: dep,
  debuggereventextrainfo?: debuggereventextrainfo
) {
  let shouldtrack = false
  if (effecttrackdepth <= maxmarkerbits) {
    if (!newtracked(dep)) {
      dep.n |= trackopbit // set newly tracked
      shouldtrack = !wastracked(dep)
    }
  } else {
    // full cleanup mode.
    // 如果依赖中并不存当前的 effect 副作用函数
    shouldtrack = !dep.has(activeeffect!)
  }
 
  if (shouldtrack) {
    // 将当前的副作用函数收集进依赖中
    dep.add(activeeffect!)
    // 并在当前副作用函数的 deps 属性中记录该依赖
    activeeffect!.deps.push(dep)
    if (__dev__ && activeeffect!.ontrack) {
      activeeffect!.ontrack(
        object.assign(
          {
            effect: activeeffect!
          },
          debuggereventextrainfo
        )
      )
    }
  }
}

trigger 派发更新

对属性进行赋值时,会触发代理对象的 set 拦截函数执行,如下面的代码所示:

javascript 复制代码
const obj = { foo: 1 } 
//通过代理对象p 访问 foo 属性,便会触发 set 拦截函数的执行
const p = new proxy(obj, {
  // 拦截设置操作
  set(target, key, newval, receiver){
    // 如果属性不存在,则说明是在添加新属性,否则设置已有属性
    const type = object.prototype.hasownproperty.call(target,key) ?  'set' : 'add'    
    // 设置属性值
    const res = reflect.set(target, key, newval, receiver)
    // 把副作用函数从桶里取出并执行,将 type 作为第三个参数传递给 trigger 函数
    trigger(target,key,type)   
    return res
  }
  // 省略其他拦截函数
})
 
p.foo = 2

trigger 函数

根据target和key, 从targetMap中找到相关的所有副作用函数遍历执行一遍。

javascript 复制代码
export function trigger(
  target: object,
  type: triggeroptypes,
  key?: unknown,
  newvalue?: unknown,
  oldvalue?: unknown,
  oldtarget?: map<unknown, unknown> | set<unknown>
) {
//首先检查当前 target 是否有被追踪,如果从未被追踪过,即target的依赖未被收集,则不需要执行派发更新,直接返回即可。
  const depsmap = targetmap.get(target)
  // 该 target 从未被追踪,则不继续执行
  if (!depsmap) {
    // never been tracked
    return
  }
 
  // 接着创建一个 set 类型的 deps 集合,用来存储当前target的这个 key 所有需要执行派发更新的副作用函数。
  let deps: (dep | undefined)[] = []
  //接下来就根据操作类型type 和 key 来收集需要执行派发更新的副作用函数。
  //如果操作类型是 triggeroptypes.clear ,那么表示需要清除所有依赖,将当前target的所有副作用函数添加到 deps 集合中。
  if (type === triggeroptypes.clear) {
    // collection being cleared
    // trigger all effects for target
    // 当需要清除依赖时,将当前 target 的依赖全部传入
    deps = [...depsmap.values()]
  } else if (key === 'length' && isarray(target)) {
    // 处理数组的特殊情况
    depsmap.foreach((dep, key) => {
      // 如果对应的长度, 有依赖收集需要更新
      if (key === 'length' || key >= (newvalue as number)) {
        deps.push(dep)
      }
    })
  } else {
    // schedule runs for set | add | delete
    // 在 set | add | delete 的情况,添加当前 key 的依赖
    if (key !== void 0) {
      deps.push(depsmap.get(key))
    }
 
    // also run for iteration key on add | delete | map.set
    switch (type) {
      case triggeroptypes.add:
        if (!isarray(target)) {
          deps.push(depsmap.get(iterate_key))
          if (ismap(target)) {
            // 操作类型为 add 时触发map 数据结构的 keys 方法的副作用函数重新执行
            deps.push(depsmap.get(map_key_iterate_key))
          }
        } else if (isintegerkey(key)) {
          // new index added to array -> length changes
          deps.push(depsmap.get('length'))
        }
        break
      case triggeroptypes.delete:
        if (!isarray(target)) {
          deps.push(depsmap.get(iterate_key))
          if (ismap(target)) {
            // 操作类型为 delete 时触发map 数据结构的 keys 方法的副作用函数重新执行
            deps.push(depsmap.get(map_key_iterate_key))
          }
        }
        break
      case triggeroptypes.set:
        if (ismap(target)) {
          deps.push(depsmap.get(iterate_key))
        }
        break
    }
  }
 
  const eventinfo = __dev__
    ? { target, type, key, newvalue, oldvalue, oldtarget }
    : undefined
 
  if (deps.length === 1) {
    if (deps[0]) {
      if (__dev__) {
        triggereffects(deps[0], eventinfo)
      } else {
        triggereffects(deps[0])
      }
    }
  } else {
    const effects: reactiveeffect[] = []
    // 将需要执行的副作用函数收集到 effects 数组中
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    if (__dev__) {
      triggereffects(createdep(effects), eventinfo)
    } else {
      triggereffects(createdep(effects))
    }
  }
}

triggereffects 函数

triggereffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。

javascript 复制代码
//triggereffects 函数中,遍历需要执行的副作用函数集合,如果当前副作用函数存在调度器,则执行该调度器,否则直接执行该副作用函数的 run 方法,执行更新。
export function triggereffects(
  dep: dep | reactiveeffect[],
  debuggereventextrainfo?: debuggereventextrainfo
) {
  // spread into array for stabilization
  // 遍历需要执行的副作用函数集合   
  for (const effect of isarray(dep) ? dep : [...dep]) {
    // 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
    if (effect !== activeeffect || effect.allowrecurse) {
      if (__dev__ && effect.ontrigger) {
        effect.ontrigger(extend({ effect }, debuggereventextrainfo))
      }
      if (effect.scheduler) {
        // 如果一个副作用函数存在调度器,则调用该调度器
        effect.scheduler()
      } else {
        // 否则直接执行副作用函数
        effect.run()
      }
    }
  }
}

ES6 Proxy 方法

javascript 复制代码
const p = new Proxy(person, {        //创建代理
      // 查
      get(target,propName){
          console.log(`有人读取了p身上的${propName}`)
          return target[propName];  
          //反射
          return Reflect.get(target,propName)
      },
      // 改 增
      set(target, propName, value){
          console.log(`有人修改了p身上的${propName}属性`);
          target[propName] = value;
      },
      // 删
      deleteProperty(target, propName){
          console.log(`有人删除了p身上的${propName}属性`)
          return delete target[propName];
      },
  });

createReactiveObject

之前说到的createReactiveObject,我们接下来看看createReactiveObject发生了什么。

返回 proxy

javascript 复制代码
const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet])
function createReactiveObject(
  target: unknown,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  /* 判断目标对象是否被effect */
  /* observed 为经过 new Proxy代理的函数 */
  let observed = toProxy.get(target) /* { [target] : obseved  } */
  if (observed !== void 0) { /* 如果目标对象已经被响应式处理,那么直接返回proxy的observed对象 */
    return observed
  }
  if (toRaw.has(target)) { /* { [observed] : target  } */
    return target
  }
  /* 如果目标对象是 Set, Map, WeakMap, WeakSet 类型,那么 hander函数是 collectionHandlers 否侧目标函数是baseHandlers */
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
   /* TODO: 创建响应式对象  */
  observed = new Proxy(target, handlers)
  /* target 和 observed 建立关联 */
  toProxy.set(target, observed)
  toRaw.set(observed, target)
  /* 返回observed对象 */
  return observed
}

Vue3响应式内部原理_vue3的响应式原理_monana6的博客-CSDN博客

vue3.0 响应式原理(超详细)_vue3响应式原理_我不是外星人Alien的博客-CSDN博客

【干货】这次终于把 Vue3 响应式原理搞懂了!_傲娇的koala的博客-CSDN博客

ES6 之 Proxy 介绍_es6 proxy_barnett_y的博客-CSDN博客

vue3.0 响应式原理(超详细)_vue3响应式原理_我不是外星人Alien的博客-CSDN博客

相关推荐
weixin_382395232 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__2 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.3 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~4 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华5 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子7 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅7 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni8 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学9 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端