浅析Vue数据响应式

210 阅读4分钟

Vue 响应式原理是怎么实现的?

  • 响应式的核心是通过 Object.defineProperty 拦截对数据的访问和设置

  • 响应式的数据分为两类:

    • 对象,循环遍历对象的所有属性,为每个属性设置 getter、setter,以达到拦截访问和设置的目的,如果属性值依旧为对象,则递归为属性值上的每个 key 设置 getter、setter

      • 访问数据时(obj.key)进行依赖收集,在 dep 中存储相关的 watcher
      • 设置数据时由 dep 通知相关的 watcher 去更新
    • 数组,增强数组的那 7 个可以更改自身的原型方法,然后拦截对这些方法的操作

      • 添加新数据时进行响应式处理,然后由 dep 通知 watcher 去更新
      • 删除数据时,也要由 dep 通知 watcher 去更新

methods、computed 和 watch 有什么区别?

  • 使用场景

    • methods 一般用于封装一些较为复杂的处理逻辑(同步、异步)
    • computed 一般用于封装一些简单的同步逻辑,将经过处理的数据返回,然后显示在模版中,以减轻模版的重量
    • watch 一般用于当需要在数据变化时执行异步或开销较大的操作
  • 区别

    • methods VS computed

      通过示例会发现,如果在一次渲染中,有多个地方使用了同一个 methods 或 computed 属性,methods 会被执行多次,而 computed 的回调函数则只会被执行一次。

      通过阅读源码我们知道,在一次渲染中,多次访问 computedProperty,只会在第一次执行 computed 属性的回调函数,后续的其它访问,则直接使用第一次的执行结果(watcher.value),而这一切的实现原理则是通过对 watcher.dirty 属性的控制实现的。而 methods,每一次的访问则是简单的方法调用(this.xxMethods)。

    • computed VS watch

      通过阅读源码我们知道,computed 和 watch 的本质是一样的,内部都是通过 Watcher 来实现的,其实没什么区别,非要说区别的化就两点:1、使用场景上的区别,2、computed 默认是懒执行的,切不可更改。

    • methods VS watch

      methods 和 watch 之间其实没什么可比的,完全是两个东西,不过在使用上可以把 watch 中一些逻辑抽到 methods 中,提高代码的可读性。

链接:juejin.cn/post/695082…

Vue其核心原理

Vue其核心原理就是其数据的响应式,讲到Vue的响应式原理,我们可以从它的兼容性说起,

Vue不支持IE8以下版本的浏览器,因为Vue是基于 Object.defineProperty 来实现数据响应的,而 Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因;

Vue通过Object.defineProperty的 getter/setter 对收集的依赖项进行监听, 在属性被访问和修改时通知变化,进而更新视图数据;

由于JavaScript 的限制 (以及废弃 Object.observe),Vue不能检测到对象属性的添加或删除。由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让Vue转换它,这样才能让它是响应的。

image.png

数组响应式

src/core/observer/array.js

/**
 * 定义 arrayMethods 对象,用于增强 Array.prototype
 * 当访问 arrayMethods 对象上的那七个方法时会被拦截,以实现数组响应式
 */
import { def } from '../util/index'

// 备份 数组 原型对象
const arrayProto = Array.prototype
// 通过继承的方式创建新的 arrayMethods
export const arrayMethods = Object.create(arrayProto)

// 操作数组的七个方法,这七个方法可以改变数组自身
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * 拦截变异方法并触发事件
 */
methodsToPatch.forEach(function (method) {
  // cache original method
  // 缓存原生方法,比如 push
  const original = arrayProto[method]
  // def 就是 Object.defineProperty,拦截 arrayMethods.method 的访问
  def(arrayMethods, method, function mutator (...args) {
    // 先执行原生方法,比如 push.apply(this, args)
    const result = original.apply(this, args)
    const ob = this.__ob__
    // 如果 method 是以下三个之一,说明是新插入了元素
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 对新插入的元素做响应式处理
    if (inserted) ob.observeArray(inserted)
    // 通知更新
    ob.dep.notify()
    return result
  })
})


作者:李永宁
链接:juejin.cn/post/695082… 来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。