vue2源码系列-深入响应式原理Vue.set

简介: 前面我们在 vue2源码系列-响应式原理 中介绍了 vue 中的整个响应式实现及流程,其中跳过了某些细节性的代码,现在我们再去好好学习研究一番

前面我们在 vue2源码系列-响应式原理 中介绍了 vue 中的整个响应式实现及流程,其中跳过了某些细节性的代码,现在我们再去好好学习研究一番

入口


我们在 defineReactive 函数里发现这么一段代码

Object.defineProperty(obj, key, {
  enumerable: true,
  configurable: true,
  get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
      dep.depend()
      // ???
      if (childOb) {
        childOb.dep.depend()
        if (Array.isArray(value)) {
          dependArray(value)
        }
      }
    }
    return value
  }
  //...
}
复制代码


我们知道在 dep.depend() 中会将当前订阅实例 watcher 添加进属性的 dep 中。那下面的 childOb.dep.depend() 又是干嘛的呢?

Vue.set函数


答案在于 Vue.set,我们来看看其实现吧


依旧从入口开始,在 src/core/global-api/index.js 中定义了静态属性 set

Vue.set = set
复制代码

set 的定义在 src/core/observer/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  // 如果是数组 直接调用splice方法 
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  // 如果是已经存在的属性直接返回
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 某些不允许开发者添加属性的对象
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 非监测属性直接返回
  if (!ob) {
    target[key] = val
    return val
  }
  // 监测新值
  defineReactive(ob.value, key, val)
  // 通知更新
  ob.dep.notify()
  return val
}
复制代码


咋一看 set 函数并不复杂,无非是判断一些不同的情况。如果是真正符合条件参数的再为其添加属性,监测新值,同时再调用下 dep 的通知。


但其复杂之处就在于 ob.dep.notify()。之前我们学习vue响应式的时候说到当值更新时会触发属性 set 函数并调用属性对应的 dep.notify,而这里调用的是属性值的 dep -> ob.dep, 其定义在 Observer 类中

// Observer contructor
this.dep = new Dep()
复制代码


我们有必要梳理下 Vue.set 的实现流程

Vue.set实现流程


假设在模板中有这么一段

<span>{{deep.name}}</span>
复制代码
data() {
  return {
    deep: {}
  }
}
复制代码

  1. defineReactive 函数中会为属性值 {} 生成新的监测实例并返回
let childOb = !shallow && observe(val)
复制代码


其中 childObnew Observer() 中为其添加了 dep 属性指向新的 dep 实例

  1. 劫持属性 deepget 函数

  2. 渲染函数调用 this.deep.name 触发 deepget 函数,将当前订阅者添加进了 childObdep 的订阅者中
if (childOb) {
  childOb.dep.depend()
  // 数组的情况我们待会再讲
  if (Array.isArray(value)) {
    dependArray(value)
  }
}
复制代码

注意我们没有劫持deep.name的get,因为此时并deep是个空对象,没有任何属性值,更不会遍历属性为其添加get


  1. 开发者调用 Vue.set(deep, 'name', 'xxx') 添加 name 属性

  2. Vue.set 函数中调用 ob.dep.notify() 通知更新


这里的 ob 实际指向了 deep 的值 {},之前我们在 为其添加了 dep 属性,在 中添加了和 deep 相同的 watcher 实例。所以 ob.dep.notify() 通知了 watcher 更新,和 deepsetdep.notify() 本质是一样的。


为数组元素添加属性


继续来看这段还未破解的代码

// defineReactive -> get
if (Array.isArray(value)) {
  dependArray(value)
}
复制代码


为什么要有这段代码呢?我们来看看这么个情况

<span>{{deep[0].name}}</span>
复制代码
data() {
  return {
    deep: [{}]
  }
}
复制代码
Vue.set(this.deep[0], 'name', 'xxx')
复制代码


我们为 deep[0] 赋值新属性 name,按照我们之前的学习成果来看看是否会触发更新


  1. 是否会触发属性的 dep.notify()


我们现在相当于触发 deep[0].nameset 函数,但是之前没有 name 属性,想必也不会触发自定义 set 了,所以不会更新


  1. 是否会在 Vue.set 中触发 ob.dep.notify()?


答案是会的,但是我们应该看看此时的 ob 是?


obdeep[0] 的值 {},但是按照 childOb.dep.depend() 来看,其是在遍历 deep 的属性值函数 defineReactive 中为其添加订阅的。


根据 vue 实现原理,我们是不会对数组进行属性遍历的

// Observe
if (Array.isArray(value)) {
  if (hasProto) {
    protoAugment(value, arrayMethods)
  } else {
    copyAugment(value, arrayMethods, arrayKeys)
  }
  // 在这边将遍历value进行observe 不会走walk进行defineReactive
  this.observeArray(value)
} else {
  this.walk(value)
}
复制代码


所以虽然调用了 ob.dep.notify(),但实际 ob.dep 并没有添加渲染函数的 {{deep[0].name}} 对应的 watcher。但是通过神来一笔


// defineReactive -> get
if (Array.isArray(value)) {
  dependArray(value)
}
复制代码


这样在 defineReactive(vm._data, deep, [{}]) 的时候就会触发 dependArray(value) 完成数组元素 dep 对应 watcher 实例的订阅


dependArray


我们也来稍微看看 dependArray 的实现

function dependArray (value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i]
    // 编辑递归数组元素完成e.__ob__.dep.depend() 实现上上...级属性的 get 中订阅实例的订阅
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}
复制代码


总结


Vue.set 的实现看起来并不复杂,但是其中的弯弯绕绕还是比较绕人的。本篇的分析其实是比较全面的,但是因为表达原因可能有些地方比较难看懂,大家可以一起交流探讨,错误的地方希望指正。下篇将继续分析响应式原理的其它细节。


相关文章
|
2天前
|
JavaScript
Vue3中props的原理与使用
Vue3中props的原理与使用
10 0
|
2天前
|
JavaScript 前端开发 开发者
响应式原理:Vue 如何跟踪数据变化
【4月更文挑战第22天】Vue 的响应式系统是其核心,通过数据双向绑定实现视图与数据同步。依赖收集和观测数据使Vue能跟踪变化,变化通知组件更新视图。高效的更新策略如批量更新和虚拟DOM提升性能。组件化和可组合性支持有效通信和代码复用,强调数据驱动开发。开发者应合理组织数据、谨慎处理变更并充分利用组件化优势,以提高效率和用户体验。
|
2天前
|
JavaScript 前端开发
vue中nextTick使用以及原理
vue中nextTick使用以及原理
7 0
|
2天前
|
JavaScript 前端开发
深入了解前端框架Vue.js的响应式原理
本文将深入探讨Vue.js前端框架的核心特性之一——响应式原理。通过分析Vue.js中的数据绑定、依赖追踪和虚拟DOM等机制,读者将对Vue.js的响应式系统有更深入的理解,从而能够更好地利用Vue.js构建灵活、高效的前端应用。
|
2天前
|
搜索推荐 算法 数据挖掘
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
探索 Elasticsearch 8.X Terms Set 检索的应用与原理
12 0
|
2天前
|
开发框架 JavaScript 算法
了解vue3的基本特性和底层原理
Vue3的底层原理涵盖了响应式系统的Proxy-based实现、组件的模板编译与渲染更新机制、组合式API带来的逻辑组织变革,以及其他关键特性的具体实现。这些原理共同构成了Vue3强大、高效、灵活的现代前端开发框架基础。
28 2
|
2天前
|
JavaScript
Vue3中props的原理与使用
Vue3中props的原理与使用
|
2天前
|
JavaScript
vue2中$set的原理_它对object属性做了啥?
vue2中$set的原理_它对object属性做了啥?
11 1
|
2天前
|
JavaScript 前端开发 开发者
Vue的响应式原理:深入探索Vue的响应式系统与依赖追踪
【4月更文挑战第24天】Vue的响应式原理通过JavaScript getter/setter实现,当数据变化时自动更新视图。它创建Watcher对象收集依赖,并通过依赖追踪机制精确通知更新。当属性改变,setter触发更新相关Watcher,重新执行操作以反映数据最新状态。Vue的响应式系统结合依赖追踪,有效提高性能,简化复杂应用的开发,但对某些复杂数据结构需额外处理。
|
2天前
|
JavaScript 前端开发 开发者
浅谈Vue 3的响应式对象: ref和reactive
浅谈Vue 3的响应式对象: ref和reactive