Vue3响应式内部原理

响应式内部原理—Vue的核心机制之一

在这里插入图片描述

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

响应式的实现方式就是劫持数据,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函数收集依赖的实现


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 属性中记录该依赖。
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 拦截函数执行,如下面的代码所示:

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中找到相关的所有副作用函数遍历执行一遍。

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 方法,执行更新。
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()
      }
    }
  }
}
monana6
关注 关注
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
写文章

热门文章

  • opencv实现车牌识别 6691
  • vue3-Hook 4866
  • Vue3watch 和 watchEffect 4253
  • 汇总tf.keras模型层layers 3453
  • L2范数归一化 2639

分类专栏

  • 前端小技巧 4篇
  • Vue3 22篇
  • Vue2原理 3篇
  • js的一些记录 2篇
  • Mock 1篇
  • 前端好的页面代码逻辑实现 1篇
  • css知识点 1篇
  • 小程序 1篇

最新评论

  • 统信OUS安装node, npm,vue (亲测有效)

    CSDN-Ada助手: 推荐 Vue入门 技能树:https://edu.csdn.net/skill/vue?utm_source=AI_act_vue

  • Schatten-p

    Monster_2020.10.6: 同学请问你找到了吗,我也在找表情包

  • opencv实现车牌识别

    波力海苔-: 2221009929@qq.com 谢谢大佬

  • Schatten-p

    今天也要喝酸奶i: 博主有没有schatten-p范数最小化的matlab代码,不知道这个schatten怎么用,没找到什么学习的资料表情包

  • numpy学习(2)

    纪江淮OvO: 一篇文章懂广播

大家在看

  • Python3 监控端口:使用 socket 库 242
  • WARNING:pip is configured with locations that require TLS/SSL, however the ssl module in python 解决方案 7
  • Python基础—f字符串就这么简单 160
  • HPC应用&分子动力学软件HOOMD-blue详细安装使用教程
  • Python 传输 Hex

最新文章

  • 跨域:脱离浏览器,服务器之间是不存在跨域问题的
  • Promise 异步执行顺序不对是怎么了呢?
  • 选项式API和组合式API的区别
2024年6篇
2023年20篇
2022年19篇
2021年12篇
2020年14篇
2019年4篇
2018年12篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

PHP网站源码深圳百搜词包大运营销网站坪地如何制作网站盐田关键词按天计费南山网站优化坂田英文网站建设东莞百度标王横岗SEO按效果付费永湖营销网站大运网页设计永湖百度seo龙华企业网站建设南山网站推广南山百搜词包龙华网站推广方案南山网站设计坪地百搜词包爱联网站改版坑梓网络广告推广木棉湾SEO按天扣费爱联网站推广方案木棉湾阿里店铺运营荷坳阿里店铺托管观澜百搜标王永湖网站关键词优化塘坑模板制作丹竹头设计公司网站松岗网站设计布吉网站改版大运网站优化推广歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

PHP网站源码 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化