Vue3响应式原理 私

这篇具有很好参考价值的文章主要介绍了Vue3响应式原理 私。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。Vue3响应式原理 私,Vue3.0X,vue.js,前端,javascript

响应式的实现方式就是劫持数据,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 属性中记录该依赖。

// 收集副作用函数,在 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 方法,执行更新。

//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 方法

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

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博客


 文章来源地址https://www.toymoban.com/news/detail-686257.html

到了这里,关于Vue3响应式原理 私的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Vue3响应式原理 私

    响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。 响应式的实现方式就是劫持数据,Vue3的reactive就是通过Proxy劫持数据,由于劫持的是整个对象,所以可以检测到任何对象的修改,弥补了2.0的不足。 名词解释: **副作用函数:**函数的

    2024年02月10日
    浏览(39)
  • 手写Vue3响应式数据原理

    我们想要对一个对象数据进行处理,从而实现更改dom。但如何更改对一个对象数据进行更改呢? vue2 的双向数据绑定是利⽤ES5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持 结合 发布订阅模式的⽅式来实现的。 vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀

    2024年02月11日
    浏览(49)
  • 前端Vue篇之Vue3响应式:Ref和Reactive

    在Vue3中,响应式编程是非常重要的概念,其中 Ref 和 Reactive 是两个关键的API。 Ref : Ref 用于创建一个响应式的基本数据类型,比如数字、字符串等。它将普通的数据变成响应式数据,可以监听数据的变化。使用 Ref 时,我们可以通过 .value 来访问和修改数据的值。 Reactive :

    2024年04月25日
    浏览(50)
  • Vue3.0中的响应式原理

    实现原理: - 对象类型:通过 ``Object.defineProperty()``对属性的读取、修改进行拦截(数据劫持)。 - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。 存在问题: - 新增属性、删除属性, 界面不会更新。 - 直接通过下标修改数组, 界面

    2023年04月17日
    浏览(50)
  • 【Vue3响应式原理#01】Reactivity

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌 欢迎各位ITer关注点赞收藏🌸🌸🌸 以下是柏成根据Vue3官方课程整理的响应式书面文档 - 第一节,课程链接在此:Vue 3 Reactivity - Vue 3 Reactivity | Vue Mastery,本文档可作为课程的辅助材料,

    2024年02月08日
    浏览(41)
  • Vue2和Vue3响应式原理实现的核心

    Vue.js 是一个开源的渐进式 JavaScript 前端框架,主要用于构建用户界面和单页应用程序(SPA)。Vue.js 可以轻松地与其他库或现有项目集成使用,并被认为是开发响应式数据驱动的现代 Web 应用的一种有效方式。 Vue.js 的核心特点: 响应式数据绑定:Vue.js 可以通过对数据进行双

    2024年02月08日
    浏览(47)
  • 【源码系列#05】Vue3响应式原理(Ref)

    ref: 接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value 可以将 ref 看成 reactive 的一个变形版本,这是由于 reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应, 如果传入 ref 的

    2024年02月03日
    浏览(35)
  • 【源码系列#02】Vue3响应式原理(Effect)

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌 欢迎各位ITer关注点赞收藏🌸🌸🌸 Vue3中响应数据核心是 reactive , reactive 的实现是由 proxy 加 effect 组合 ,上一章节我们利用 proxy 实现了一个简易版的 reactive,# 【源码系列#01】Vue3响应

    2024年02月05日
    浏览(42)
  • 【源码系列#01】vue3响应式原理(Proxy)

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌 欢迎各位ITer关注点赞收藏🌸🌸🌸 在学习 Vue3 是如何进行对象的响应式代理之前,我想我们应该先去了解下 ES6 新增的API Proxy 与 Reflect ,可参考【Vue3响应式入门#02】Proxy and Reflect 。之

    2024年02月05日
    浏览(50)
  • vue2、vue3、react响应式原理、组件声明周期阐述与对比

    响应式原理: Vue.js 的响应式原理是通过使用 Object.defineProperty 函数来实现的。在 Vue.js 中,当一个对象被传入 Vue 实例的 data 选项中时,Vue.js 会将这个对象的属性转换为 getter 和 setter,以便在属性被访问或修改时能够触发相应的更新。 具体来说,Vue.js 会在实例化过程中递归

    2024年02月06日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包