Vue2 响应式原理

568 阅读4分钟

vue2系列的文章分为reactivity,compile,runtime等不同模块,从源码入手,省略掉部分代码,只给出核心逻辑,旨在阐明vue的工作原理。

reactivity

Compile

vdom和diff

3种类型的watcher对比

一、源码阅读

vue作为一套用于构建用户界面的渐进式框架,数据的变化就能够驱动相应的视图更新,将我们从繁琐的DOM操作中解放出来。所以,理解vue的响应式原理能够使我们在开发工作中更加得心应手。

我们从vue的实例化过程new Vue ( )说起:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  //...
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

Vue函数在src\core\instance\index.js中被定义, initMixin(Vue)在Vue的原型链上添加_init方法。 Vue实例化过程中会调用 _init 方法,下面通过 _init的调用链说明 vue2 的响应式原理。

_init(options) --> initState(vm) --> initData(vm) --> observe(data,true)

其中data是options中的data函数返回的对象。如果data不是object类型,直接返回。如果data包含 __ob__属性,说明已经存在观察者,直接将其返回。否则,返回新的观察者。

 function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

用传入的 data 实例化 Observer 对象,是响应式的重点逻辑( 敲黑板!!!),在src\core\observer\index.js中被定义,构造函数如下:

constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // 使用Object.defineProperty定义value __ob__属性的值,writable,configurable为true,enumerable为false
    def(value, '__ob__', this) 
    // 如果data是数组,根据不同的浏览器环境(IE浏览器这种不存在__proto__),将push、pop、unshift、shift、splice、sort、reverse七个方法在数组原型方法的基础添加响应式代理
    // 然后递归遍历数组的每一项,生成它们的观察者对象
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }

如果是普通对象类型,调用walk方法深度优先遍历,对数据进行拦截。

function walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

defineReactive中通过object.defineProperty拦截数据的getset

function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()    // 实例化属性的订阅器

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    // 属性不能被删除或重新设置时,直接返回
    return
  }

  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]       // getter不存在,且未传参value时,定义val的值
  }

  let childOb = observe(val)      // 深度优先递归遍历
  Object.defineProperty(obj, key, {    // 拦截getter和 setter属性
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
    // 属性嵌套的其他属性,都做依赖收集
    // 属性订阅器dep的subs数组添加Dep.target,即watcher,这样,属性变化可以通知所有订阅的watcher更新
    // 同时watcher的newDeps数组也添加dep,这样,watcher销毁时,可以通过在dep的subs数组中移除,取消所有的订阅
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()   
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)    // 拦截newVal的变化
      dep.notify()     // 通知所有的订阅者watcher更新
    }
  })
}

二、 实现一个简版的vue

Vue主要通过以下四个步骤实现数据的双向绑定:

  1. 实现一个解析器Compile:

    解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染视图。并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者。当数据变化时,订阅者收到通知,调用更新函数。

  2. 实现一个监听器Observe:

    递归遍历数据对象,利用Object.defineProperty给属性添加getter和setter。数据变化时,触发setter,也就是监听到了数据变化。

  3. 实现一个订阅者Watcher:

    Watcher是Observer和Compile之间通信的桥梁,主要的任务是订阅Observer中数据变化的消息,触发Compile中对应的更新函数。

  4. 实现一个订阅器Dep:

    利用发布-订阅模式,收集订阅者Watcher,对监听器Observer和Watcher统一管理。

    vue响应式原理
    简版的vue也通过实现以上四步来实现,详细内容请参考mini-vue2