Vue2源码阅读(一):响应式原理

一、阅读准备

阅读Vue.js代码前,需要准备:

  • 仓库代码,方便加注释和多段关键代码可以同时阅读
  • 打包后未压缩的代码,方便打断点,看代码执行情况

做好以上准备后,我们写一个demo





    learn Vue



    
hello vue.js
{{count}}
点击我
{{item}}

用 Chrome 打开这个demo后,我们在new Vue前打一个断点,就可以看到Vue是如何执行的了。

二、响应式原理

2.1 代码阅读

根据第一个断点找到了Vue的入口function Vue(),然后从断点进入Vue.prototype._init,代码如下:

Vue.prototype._init = function (options?: Object) {
    // 这里删去了无关代码
    
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    // 执行 beforeCreate 钩子
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    // 执行 created 钩子
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }

通过Vue生命周期图得知Vue是在breforeCreatecreated这两个钩子之间完成reactivity,于是我们进入initState(vm)方法,代码如下:

function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

通过上述代码不难得知VueinitData中完成了对options.data的改造,使其具有响应式功能,于是我们进入initData

function initData (vm: Component) {

  /**** begin: 从vm中取出data对象 ****/
  let data = vm.$options.data
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  /**** end ****/
  
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  const methods = vm.$options.methods
  let i = keys.length
  while (i--) {
    // 判断 data 中是否有和 props 和 methods 重名的属性
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${key}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else if (!isReserved(key)) {
      // 使用 vm 代理 vm._data
      // 通过 vm[prop] 访问 vm._data[prop]
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 在这里使用观察者模式对 data 进行封装
  observe(data, true /* asRootData */)
}

上述代码完成了(1)把datavm.data中取出来转为对象;(2)对props, methods属性进行检查,看是否和data属性冲突;(3)调用observedata进行响应式封装。下面我们找到observe的代码:

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
function observe (value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 如果存在 __ob__ ,则返回已存在的观察者
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

上面代码的注释已经很清楚的讲述了这个函数的用处:为传入的value(这里传入的是vm.data)创建一个observer对象,如果value已经有observer了,那就返回已有的observer实例。这里又调用了Observer构造函数,代码如下:

class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // 把 observer 挂载到 value.__ob__ 上
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 数组方法重写,支持响应式
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 递归 observe 数组, 所以直接修改数组的元素值是具有响应性质的
      this.observeArray(value)
    } else {
      // 让 vm._data上的每个属性都支持响应式
      this.walk(value)
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk (obj: Object) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray (items: Array) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

生成一个observer挂载到value.__ob__上,如果value为数组,那么重写该数组的原型上的方法。最后,遍历value上的属性,使其具有响应性。具体实现通过defineReactive,代码如下:

function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 属性的依赖也是保存在闭包中
  const dep = new Dep()

  // 获取 obj[key] 的属性描述对象,如果该属性不允许配置,那么直接返回
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }

  // 如果是深度reactive,还要 observe obj[key]
  let childOb = !shallow && observe(val)
  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
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        // 如果要 set 的新的值和原值相等,那么不需要 reactive
        // 第二个条件是为了避免 NaN !== NaN
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        // 注意这里,新的值是保存在闭包内的。
        val = newVal
      }
      // 修改了val后,需要修改深度监听的的 observer
      childOb = !shallow && observe(newVal)
      // 通知所有的 watcher update
      dep.notify()
    }
  })
}

这里是响应式的关键,修改对象属性的settergetter,在setter执行时,先调用对象属性原本的setter,然后通知该属性的所有的watcher做更新。

2.2 流程梳理

看了上面的代码后,脑子里就一个字:!所以我决定,这里在对实现响应式的流程做一个梳理。这里用文字对流程进行描述:

  1. reactive的入口是Vue.prototype._init里调用的initState(vm)
  2. initState中调用initData。主要把data对象从data函数中取出来挂载到vm._data上,并使用vm代理vm._data;检查propsmethods中是否有属性名和data属性名冲突;调用observe(data)
  3. observe调用new Observer(value)(这里的value是被观察对象)生成一个Observer对象挂载到value.__ob__上。
  4. Observer构造函数中重写了数组原型链的方法使其支持reactive,然后调用walk遍历vm._data执行defineReactive,另外,使用new Dep生成一个依赖对象挂载到value.__ob__.dep上用来保存依赖。
  5. defineReactive里声明了一个闭包变量dep这个变量是真正保存属性watcher的,在调用属性getter的时候,如果当时存在Dep.target,就会让Dep.target把自身加入到前面申明的闭包变量dep.sub中。
  6. 当属性值发生改变时,会调用dep.notify通知sub中的所有watcher让其更新。

总结

  1. 真正实现观察者模式的是DepWatcher,如果能改下名字,那就更好了。。。
  2. 因为调用defineReactive时默都是深响应,data中每一个属性都会递归转为settergetter,着实有点占用性能。
  3. Observer开始实现响应式。
  4. created后,vm._data初始化完成,适合请求数据。
  5. 模板渲染的时候,会通过调用属性的getter添加依赖。

你可能感兴趣的:(Vue2源码阅读(一):响应式原理)