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


相关文章
|
26天前
|
存储 算法 Java
解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
41 3
|
17天前
|
缓存 JavaScript 搜索推荐
Vue SSR(服务端渲染)预渲染的工作原理
【10月更文挑战第23天】Vue SSR 预渲染通过一系列复杂的步骤和机制,实现了在服务器端生成静态 HTML 页面的目标。它为提升 Vue 应用的性能、SEO 效果以及用户体验提供了有力的支持。随着技术的不断发展,Vue SSR 预渲染技术也将不断完善和创新,以适应不断变化的互联网环境和用户需求。
32 9
|
19天前
|
API
vue3知识点:响应式数据的判断
vue3知识点:响应式数据的判断
25 3
|
2月前
|
缓存 JavaScript 前端开发
「offer来了」从基础到进阶原理,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系
该文章全面覆盖了Vue.js从基础知识到进阶原理的48个核心知识点,包括Vue CLI项目结构、组件生命周期、响应式原理、Composition API的使用等内容,并针对Vue 2与Vue 3的不同特性进行了详细对比与讲解。
「offer来了」从基础到进阶原理,从vue2到vue3,48个知识点保姆级带你巩固vuejs知识体系
|
23天前
|
缓存 JavaScript UED
优化Vue的响应式性能
【10月更文挑战第13天】优化 Vue 的响应式性能是一个持续的过程,需要不断地探索和实践,以适应不断变化的应用需求和性能挑战。
29 2
|
27天前
|
JavaScript 前端开发
Vue 2 和 Vue 3 之间响应式区别
10月更文挑战第7天
32 2
|
1月前
|
JavaScript 前端开发 网络架构
如何使用Vue.js构建响应式Web应用
【10月更文挑战第9天】如何使用Vue.js构建响应式Web应用
|
1月前
|
JavaScript 前端开发
如何使用Vue.js构建响应式Web应用程序
【10月更文挑战第9天】如何使用Vue.js构建响应式Web应用程序
|
1月前
|
JavaScript UED
Vue双向数据绑定的原理
【10月更文挑战第7天】
|
1月前
|
JavaScript 前端开发 数据安全/隐私保护
前端技术分享:使用Vue.js构建响应式表单
【10月更文挑战第1天】前端技术分享:使用Vue.js构建响应式表单

热门文章

最新文章