Vue2 响应式原理

Vue2 响应式原理

⭐️ 参考vue官方阐述:👉点这里

响应式数据的最终目标,是当对象本身或对象属性发生变化时,将会运行一些函数,最常见的就是render函数。

在具体的实现上,Vue 做了几个核心模块

  1. Observer
  2. Dep
  3. Watcher
  4. Scheduler

⭐️ 接下来让我们一起来探索一下吧!🌈

Observer

🌈Observer要实现的目标非常简单,就是把一个普通的对象转换为响应式的对象
为了实现这一点,Observer把对象的每个属性通过Object.defineProperty转换为带有gettersetter的属性,这样一来,当访问或设置属性时,vue就可以做一些其他的事情。下面看一段代码👇

// 核心代码
Object.defineProperty(obj,key,{
    enumerable: true,
    configurable: true,
    get: function() {
        // 其他处理逻辑...
        return value;
    },
    set: function(newVal) {
        // 其他处理逻辑...
        val = newVal;
    },
})

⭐️ 在组件生命周期中,这件事发生在beforeCreate之后,created之前。
具体实现上,它会递归遍历对象的所有属性,以完成深度的属性转换。

🌈由于遍历时只能遍历到对象的当前属性,如果后续需要动态增加或删除的属性,vue监测不到,因此vue提供了$set$delete两个实例方法,可以通过这两个实例方法对已有响应式对象动态添加或删除属性。具体实现如下👇

function set(target: Array<any> | Object, key: any, val: any){
    // 其他处理逻辑...
    target[key] = val;
    return val
}

function del (target: Array<any> | Object, key: any){
    // 其他处理逻辑...
    if (!hasOwn(target, key)) {
        return
    }
    delete target[key]
}

⭐️ 对于数组的处理,vue会更改它的隐式原型,之所以这样做,是因为vue需要监听那些可能改变数组内容的方法

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']

methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  // Define a property.
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    switch (method) {
      case 'push':
      case 'unshift':
        // 其他处理逻辑...
        break
      case 'splice':
        // 其他处理逻辑...
        break
    }
    // 其他处理逻辑...
    return result
  })
})

🌈 总之,Observer设计的目的就是要让一个对象的属性的读取、赋值,内部数组的变化vue能够监测的到。

Dep

⭐️当vue读取属性时要做什么处理,而属性变化时要做什么处理,这些问题需要借助Dep来解决。

Dep即Dependency,表示依赖的意思。

⭐️在上面讲到的 Observer 会给每个属性去设置 gettersetter,这两个函数内部并不是简单的将值修改或返回,内部会实例化一个dep,每个Dep实例都有能力做以下两件事:

  1. 记录依赖:记录哪些地方用到了我
  2. 派发更新:我变了,需要通知那些用到我的地方
class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // 其他处理逻辑...
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

🌈当访问属性值时,运行getter,调用dep.depend(),递归收集依赖,保存在 subs 数组中

if (Dep.target) {
    dep.depend()
    if (childOb) {
        childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
        }
    }
}

🌈当修改属性值时,运行setter,调用dep.notify(),派发更新,通知用到的地方更新。

Watcher

观察者(订阅者),当页面的依赖数据变化,需要去通知用到该数据的地方更新,通过dep.notify()派发更新,watcher就是用来做这个数据的订阅者。数据变化不会立即执行函数,而是将函数交给watcher,通过dep.depend()记录依赖,Dep上就会有记录,表示有一个watcher用到了这个数据。watcher内部需要自己实例化一个dep,当访问数据时把watcher添加到depsubs中,调用 update函数 更新数据。

class Watcher {
  constructor (vm, key, cb) {
    this.vm = vm

    // data 中的属性名称
    this.key = key
    // 当数据变化的时候,调用 cb 更新视图
    this.cb = cb
    // 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到dep 的 subs 中
    Dep.target = this
    // 触发一次 getter,让 dep 为当前 key 记录 watcher
    this.oldValue = vm[key]
    // 清空 target
    Dep.target = null
  }
  update () {
    const newValue = this.vm[this.key]
    if (this.oldValue === newValue) {
      return
    }
    this.cb(newValue)
  }
}

Scheduler

🌈 调度器。当dep通知watcher去运行相应的函数时,有可能导致这个函数多次运行,换句话说就是同一个watcher添加了多次。为了解决这个问题,Vue做了一个Scheduler来处理这个问题。

const MAX_UPDATE_COUNT = 100
// 定义每一个Watcher数组(队列)
const queue: Array<Watcher> = []

function queueWatcher(watcher: Watcher) {
  // 将 watcher添加到队列中,有相同id的watcher跳过
  const id = watcher.id
  if (has[id] != null) {
    return
  }

  if (watcher === Dep.target && watcher.noRecurse) {
    return
  }
  // 其他处理逻辑...
  queue.push(watcher)
  // 内部通过nextTick方法加到为队列
  nextTick(flushSchedulerQueue)
}

⭐️ 综上所述,Scheduler 内部维护一个Watcher的队列,该队列中的同一个watcher只会出现一次,并且Watcher队列不是立即去执行,而是通过nextTick方法将这些watcher放到微队列中依次执行。

总结

🌈数据响应式首先是将数据通过Object.defineProperty把数据添加gettersetter,再通过Dep运行getter去收集依赖,运行setter去派发更新,Watcher接到通知,不立即执行函数,而是交给一个Schedule调度器去运行函数,做到数据、页面的更新。⭐️

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值