Vue源码阅读(6):响应式原理讲解的前期准备

 我的开源库:

Vue 最终渲染的真实 DOM 是由模板字符串和数据(状态)决定的,一旦状态改变,页面就会自动重新渲染。这一特性是 Vue 的响应式系统赋予的,响应式系统主要围绕数据(状态)做了两件事:(1)收集依赖;(2)变化侦测。

1,什么是收集依赖

如果组件 componentA 使用了数据 dataB 的话,我们就可以说组件 componentA 依赖了数据 dataB。此时,应该把这个依赖收集保存起来,以供后续变化侦测调用触发。

2,什么是依赖

在 Vue 中,有一个专门的类用来表示依赖,Watch,他被定义在 core/observer/watcher.js 中。这个类封装了当状态改变时,Vue 应该做的操作。例如与渲染有关的渲染 watcher。

let updateComponent = () => {
   // vm._render() 函数的执行结果是一个 VNode
   // vm._update() 函数执行虚拟 DOM 的 patch 方法来执行节点的比对与渲染操作
   vm._update(vm._render(), hydrating)
}

// 这里的 Watcher 实例是一个渲染 Watcher,组件级别的
vm._watcher = new Watcher(vm, updateComponent, noop)

上面这个 watcher 就封装了当组件使用的状态变化时,Vue 应该做的操作 updateComponent,该函数首先执行 render 函数获取最新的虚拟 DOM,然后调用 _update 进行页面的渲染,_update 函数会对新旧虚拟 DOM 进行对比,然后对有差异的地方进行最小程度 DOM 操作。

3,依赖收集到哪里

在 Vue 中,有一个专门用于收集依赖的类,Dep 类,它被定义在 core/observer/dep.js 中。其内部维护了一个数组,用于存储依赖(Watcher 类的实例)。一个 Dep 的实例和一个属性对应,也就是说这个 Dep 实例存储了这个属性的依赖,如果这个属性的值变化了的话,Vue 就会执行这个属性对应 Dep 实例的 notify 方法,该方法会遍历执行 subs 数组中 Watcher 实例的 update 方法。

class Dep {
  // 用于收集依赖的数组
  subs: Array<Watcher>;

  constructor () {
    // 初始化保存依赖的数组 subs
    this.subs = []
  }

  // 触发 subs 数组中依赖的更新操作
  notify () {
    // 数组的 slice 函数具有拷贝的作用
    const subs = this.subs.slice()
    // 遍历 subs 数组中的依赖项
    for (let i = 0, l = subs.length; i < l; i++) {
      // 执行依赖项的 update 函数,触发执行依赖
      subs[i].update()
    }
  }  
}

4,依赖收集的时机

依赖收集发生在使用这个数据的时候,例如在模板中使用某个数据渲染页面。在 JS 中,有两种方案监控数据被使用,第一种是借助 Object.defineProperty,第二种是借助 ES6 中的 Proxy,Proxy 的解决方案很完美,但是由于浏览器的支持并不理想,所以 Vue 2 中使用的是 Object.defineProperty。

如果数据的某个属性被使用了的话,JS 会调用这个属性对应的 get 函数,我们可以在这个 get 函数中进行依赖的收集,简要的代码如下:

// 原始的 get 操作
const getter = property && property.get

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 触发执行上面拿到的 getter
      const value = getter ? getter.call(obj) : val
      / 下面是依赖收集的操作 /
      // 如果 Dep 上的静态属性 target 存在的话
      if (Dep.target) {
        // 向 dep 中添加依赖,依赖是 Watcher 的实例
        dep.depend()
        if (childOb) {
          // childOb.dep 用来存储数组类型值的依赖
          childOb.dep.depend()
        }
      }
      // getter 返回值
      return value
    },
  })

上面代码中很有意思的一点是:数组类型值和对象类型值依赖存储的位置并不一样,这是因为数组类型值和对象类型值变化侦测的位置不同,接下来说说变化侦测。

5,变化侦测

依赖收集完成之后,如果数据变化了的话,会触发这个数据对应 dep 实例的 notify 方法,在 notify 方法中执行该数据依赖(Watcher 实例)的 update 方法。

为完成上面的操作,我们需要知道什么时候数据发生了变化,这就是变化侦测。

对象类型值和数组类型值的变化侦测采用了不同的方式。对象类型值借助 Object.defineProperty 中的 set 实现变化侦测。但是由于数组类型的值可以使用原型上的方法(push、pop、sort 等)变更数组,Object.defineProperty 无法监控数组原型方法的使用,所以数组类型值的变化侦测无法使用 Object.defineProperty。Vue 的做法是重写数组类型值的原型方法,这样当调用数组的原型方法时,我们就可以在重写的原型方法中执行相应的操作

 6,总结

  • 收集依赖和变化侦测都是围绕数据(状态)展开的。
  • 对象类型和数组类型的依赖收集都是借助 Object.defineProperty 中的 get 实现的。
  • 对象类型和数组类型的变化侦测使用了不同的方案。对象类型使用 Object.defineProperty 中的 set 实现,数组类型的实现方法是重写了原型上的方法,在重写的原型方法中执行相应的变化侦测的逻辑。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值