Vue2.0 响应式的执行过程 以及首次渲染的过程

16 篇文章 1 订阅

vue2.0 的响应式原理网络上有很多的资源,这里就学习一下 源码中响应式的执行过程(这里还包括了 vue 首次渲染的过程)

如果实在没基础的话,可以先看这一篇: 实现乞丐版的 vue data + method + computed

需要注意的是,defineProperty 的 get set 是这一棵大树的基石,如果在 某个步骤不理解的话,想一想这个也许你就能想明白

1、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'

// 不使用 class 是为了方便使用 prototyp
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 调用 init
  this._init(options)
}

// 注入 _init
initMixin(Vue)
// 注册 $data/$props/$set/$delete/$watch
stateMixin(Vue)
// 初始化事件相关方法
// $on/$once/$off/$destory
eventsMixin(Vue)
// 初始化生命周期的混入
lifecycleMixin(Vue)
// 混入render $nextTick _render
renderMixin(Vue)

export default Vue

这里各种 mixin 的作用都有注释,重点来看 这里的 _init 的过程

说句题外话,无论是 react 还是 vue ,初始化的函数 都是 function ,而不是 class ,这是为了方便 可以使用 prototype 进行拓展

2、Vue.prototype._init

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    // 当前 的唯一标识符
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    // 当前实例是VUE实例,防止以后被 observe ,也就是不做处理
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      // 把传入的 options 和 构造函数的 options 合并
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 建立父子、祖先关系
    initLifecycle(vm)
    // 用来构建事件响应
    initEvents(vm)
    // 挂载 h 函数
    initRender(vm)
    // 触发 beforeCreate 生命周期函数
    callHook(vm, 'beforeCreate')
    // 循环遍历 parent 的 _provided,然后获取 inject
    initInjections(vm) // resolve injections before data/props
    // 初始化props methods data
    initState(vm)
    // 给当前节点加上 _provided
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')  // 调用 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)
    }
  }
}

主要来看 initState vm.$mount 这里的其他函数都不在这里次的学习范围之内

3. initState

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

initProp 和 initMethods 就不深入了,这里讲一下就好了

initProp

  1. initProps 的作用 设置 这个值为 响应式的

  2. 但是 在 set 的函数中,设置一个 报错,也就是禁止 给 props 赋值,所以这个值 只能获取,不能 修改

  3. 在 vm 上挂载 props的 key

initMethods

  1. 检查在 methods 中有没有存在 和 props 同名的,存在就报错
  2. 如果 当前的对象中,不是方法,就返回一个空方法,否则就绑定到 vm

4、initData

function initData (vm: Component) {
  let data = vm.$options.data
  // 当前 data 如果是一个函数,就执行 这个函数,并获取 返回值
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 如果 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
    )
  }
  // 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--) {
    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 实例中去
      proxy(vm, `_data`, key)
    }
  }
  // observe data
  // 响应式处理
  observe(data, true /* asRootData */)
}
  1. 如果 data 是一个函数,则执行 并获取
  2. 检查 和 props 和 methods 是否是 重名 
  3. 对 data 进行响应式处理 执行 observe 函数

5、observe

// 创建 observe 对象
export function observe (value: any, asRootData: ?boolean): Observer | void {
  // value 是否是 对象 或者 vm 的 对象
  if (!isObject(value) || value instanceof VNode) {
    return
  }
  let ob: Observer | void
  // 是否有 __ob__ ,这里的 __ob__ 会挂载 当前对象的 响应式 对象
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    // 是否是 vue 实例
    !value._isVue
  ) {
    // 创建一个响应式对象
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

export 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
    // 给 value 挂载 __ob__ 属性
    def(value, '__ob__', this)
    // 处理数组的响应式
    if (Array.isArray(value)) {
      if (hasProto) {
        // 让原型指向 自定义的方法
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 遍历数组中的每个成员,并给其增加响应式
      this.observeArray(value)
    } else {
      // 遍历对象中的每一个对象
      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<any>) {
    // 遍历 数组,然后 对其 进行 响应式处理
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}
  1. 重点 还在 Observer 上面,这里也就是创建一个响应式对象
  2. 然后 给当前 的对象 附加上 __ob__ 属性,指向 当前的响应式对象
  3. 分别处理 数组的 响应式 和 对象的响应式处理

6、数组的响应式处理

   ...
   if (Array.isArray(value)) {
      if (hasProto) {
        // 让原型指向 自定义的方法
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      // 遍历数组中的每个成员,并给其增加响应式
      this.observeArray(value)
    } 

   ...
  observeArray (items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }


function protoAugment (target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src
  /* eslint-enable no-proto */
}

这里对于数组的响应式处理也很简单,就是 先给 当前数组的 __proto__ 进行修改,然后 遍历数组,给每一个数组成员进行响应式处理,接下来看一下 arrayMethods 是什么

import { def } from '../util/index'

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto) // 将 arrayMethod 的原型指向 数组

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
// 遍历 methodsToPatch 数组,给对应的数组函数 进行重新的定义
methodsToPatch.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    // 修改了之后,发送通知
    ob.dep.notify()
    return result
  })
})

到了这里 ,可以发现 代码中 将 数组的 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' 进行了重新的定义,最后主要是执行了 一个 ob.dep.notify() ,也就是 通知当前的 watcher 去更新节点

7、对象的响应式处理

  walk (obj: Object) {
    const keys = Object.keys(obj)
    // 遍历每一个属性,设置为响应式数据
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }

8、defineReactive 真正的数据响应式处理的地方

// 定义一个响应式 的属性
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  const dep = new Dep()
  // 获取 obj 的属性描述符
  const property = Object.getOwnPropertyDescriptor(obj, key)
  // 不可配置的话,说明不能使用 defineProperty,返回
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  // 缓存用户的 get set
  const getter = property && property.get
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key]
  }
  // 判断是否递归观察子对象,并将子对象属性都转换为 getter/setter
  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) {  // 就是 Watcher, 每个 watcher 初始化的时候,就会进行一次赋值
        // 收集依赖
        dep.depend()
        if (childOb) {
          // 为子对象添加依赖
          // 当子对象发生了删除 和 增加的时候,发送通知
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      // 如果 用户设置了 getter ,就是用 用户的 
      const value = getter ? getter.call(obj) : val
      /* eslint-disable no-self-compare */
      // 新的值和旧的值相等,或者 是    NaN 则不执行
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      // #7981: for accessor properties without setter
      // 是只读的 ,没有 setter
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      // 如果新值是对象,那么观察新对象,并返回 
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}

纠正一个很大的误区,这里对于 defineProperty 的设置仅仅是一个设置,还没有进行响应式收集,所以到目前为止,响应式依旧没有建立完成,需要 对 模块或者 methods 等区域里面,执行了 对应响应式数据 的 getter 或者 setter,这里的 设置才会执行

  1. 获取 当前对象的 属性描述符,如果 configurable 为false 的话,就不进行响应式处理 (所以有一个 性能优化的地方,也就是 给 只展示的数据 设置 configurable 为 false,就能减少 接下来很多的响应式操作)(再ps 自己试了一下,7K条数组的数据,注册响应式花的时间 从 500ms+ -> 200ms+ 的华丽转变)
  2. 递归观察 对象的 value
  3. 通过 defineProperty 设置 对象的属性的 get 和 set 属性
  4. 当执行 get 的时候,先执行 用户设置的getter 函数,如果 Dep.target 静态属性有值,就进行依赖收集
  5. 当执行 set 的时候,限制性用户设置的 getter 函数,接着 执行用户设置的 setter 函数,如果 set 的 新值是 对象,那对该对象进行监听,最后通知 依赖更新

9、执行 mount 函数

这一部分 以及接下里的内容 建议 结合 Vue2.0 中模板编译的过程学习 来一起看,味道更好哦

回到 2.Vue.prototype._init 中,这里的最后一步执行了 vm.$mount(vm.$options.el)

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    ...
    if (vm.$options.el) {
      // 挂载整个页面
      vm.$mount(vm.$options.el)
    }
  }
}

这里的 $mount 主要是执行了 公共的 $mount 以及Vue2.0 中模板编译的过程学习 中 所说的 重新定义过的 $mount

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

所以让我们跳过模板编译的过程,最后到了 mountComponent 这一步

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 如果当前没有 render 函数,就返回一个 创建空 vnode 的函数
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  // 调用 beforeMount 回调函数
  callHook(vm, 'beforeMount')

  let updateComponent
  // vue 性能检测属性
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      // 生成 虚拟 dom
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      // 对比节点的差异,然后更新
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      // 对比节点的差异,然后更新
      vm._update(vm._render(), hydrating)
    }
  }

  // 创建 watcher 对象,渲染 watcher
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        // 这里的数据变化 如果在 mounted 之后,destory 之前,就会调用 beforeUpdate
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true 
    // 调用 mounted 钩子函数
    callHook(vm, 'mounted')
  }
  return vm
}

这里的 updateComponent  主要就是 包裹了 update 以及rander 函数然后创建了当前组件的 watcher 函数,然后调用并注入钩子函数(),而注入的 回调函数,就是 updateComponent函数

10. watcher 函数


let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep   // 侦听器里的属性 deep: true
      this.user = !!options.user   // user ,是否是 用户 watcher
      this.lazy = !!options.lazy   // 只有 computed 时候 lazy
      this.sync = !!options.sync   // 是否是 同步跟新的,在 vue 全局有一个 sync 属性可以使用
      this.before = options.before  // 这里传递的 是 beforeUpdate
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching  唯一的 id 标识符
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {  // updateComponent 函数
      this.getter = expOrFn
    } else {
      // 到这里的话,就是一个字符串,主要是 侦听器里的内容
      // 例如: watch: {'person.name': funcion ... }
      // 用来解析 这里的 字符串,获取对应的值 
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy  // 在计算属性里面,这里 就是 true ,不会在 第一时间被调用
      ? undefined
      : this.get()  // 把当前 的 对象 赋值给 Dep.target 
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // 给 Dep.target 赋值
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 执行了  updateComponent,进行页面的渲染
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      if (this.deep) { // 是否是深度监听 ,就是 侦听器里 watch 属性的 deep: true
        // 是的话,就把整个 对象 给遍历一遍
        // 这样 如果 该数据注册了监听,就会被 同一个 watcher 之内执行一遍 getter
        traverse(value)
      }
      popTarget() // 把当前的 Target 弹出
      this.cleanupDeps()  // 清除 Dep 的 watcher
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  // 缓存 对应的 Dep,然后 让 dep 缓存 当前的 watcher
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  // 清除 和 新的 dep 不同的 监听器,主要是 执行了 类似于 v-if 之类的属性之后,内部的 绑定就没有意义了
  cleanupDeps () {      
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  // 给 dep 和 forceUpdate 调用的
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true  // 依赖的 参数改变之后,computed 被调用更新的
    } else if (this.sync) {  //如果当前是 同步的,立即执行
      this.run()
    } else {
      // 把当前的 watcher 放到函数队列里面
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  // 执行 当前传入的 cb,一般是 当前组件的 watcher ,或者是 侦听器的 handler
  run () {
    if (this.active) {
      // 调用 get,渲染 watcher 的 get是 updateComponent 
      const value = this.get()
      if (
        value !== this.value ||
        // 如果是需要深度监听的,并且是 用户的watcher ,也就是监听器的时候
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {  // 如果是 用户 watcehr ,调用回调函数,而且有可能出现异常,所以调用 try catch
          try {
            // 执行 handler,并且 将新旧两个值 给传进去
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  // 用来给 computed 调用的,用来获取当前的值
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }
  /**
   * Depend on all deps collected by this watcher.
   */
  // 调用 dep 收集依赖
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  // 清除当前的 watcher
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}
  1. watcher 分为三种类型,一种是 当前组件的 渲染watcher,调用的就是当前组件的 render 函数
  2. computed 的 watcher ,有 lazy 属性,也就是 当 依赖的数据改变之后,dirty 才允许 对当前computed 执行 get
  3. 侦听器 watch 的watcher,主要是有传入 deep的话,就把当前对象 整个遍历一般,就会触发 前面定义的 getter,接着执行 传入的handler 函数

11. 事件队列queueWatcher

如果对 vue 熟悉的话,就会知道,如果执行 多次赋值操作,最终只会改变一次页面

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true  // 当前的 watcher 已经被处理了
    // 把 watcher 放到队列里面去
    if (!flushing) {
      queue.push(watcher)
    } else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    
    if (!waiting) {
      waiting = true
      // 如果是开发环境的话,直接调用 
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        flushSchedulerQueue()
        return
      }
      // 否则到 nextTick 里面去执行
      nextTick(flushSchedulerQueue)
    }
  }
}
  1. 这里可以看做是调用了 节流
  2. 当前只需要传入一个 render,对当前的render 进行缓存,但是不立即执行(nextTick)
  3. 然后 如果 在一次事件循环中,调用了 多次,只会插入一次render ,但是 render 函数 依旧会获取最新的 value

12. 执行 render 函数以及 依赖收集

vue 2.0 将 template 转换为 vnode 的 render 函数 这里就有一个简单地render 函数

  1. 通过 with 函数,将当前vue 实例 注入到render 当中,这样 能直接获取对应的数据
  2. 在获取数据的过程中,就会执行 第5 点中说道的 getter,然后 当前 Dep 有 第 9 点中的 watcher 函数
  3. 执行以来收集,这里的依赖就是当前 组件的 渲染函数,render 函数
  4. 接下来每次 执行,都会 调用 render 函数,来进行整个组件的重新渲染

13、 computed 的响应式 处理

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    // 获取 当前的 get 
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
      // 当前 没有 set 函数
    sharedPropertyDefinition.set = noop
  } else {
    // 如果是以 一个 对象形式定义的 computed,那获取里面的 get 和 set
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
        // 如果没有 定义set,而且执行了 computed 的赋值,那就报个错
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 将 相关 属性 定义到 响应式当中
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
// 在 初始化,以及被 内部依赖 调用更新的时候,dirty会被置为 true,所以内部 依赖被调用,仅仅是打开 这个可以更新的钥匙
      if (watcher.dirty) {  
// 需要 在 dirty 被改变之后,调用 这个 compute 的 get 函数,才会 调用 对应 watcher 的 get ,获得正确的值,然后 dirty 被置为 false
        watcher.evaluate()  
      }
      // 进行依赖收集
      if (Dep.target) {
        watcher.depend()
      }
      // 返回 函数计算出来的 值
      return watcher.value
    }
  }
}
  1. computed 的响应式是独立于 data 的
  2. 首先获取 用户定义的 get 和 set 属性,可以作为 响应式的 getter 和 setter
  3. 如果 用户没有 定义 set 函数,然后 给其赋值的话,报错
  4. 在 创建 Watcher 的时候,传入的 lazy 是 true ,dirty 也是 true,导致了不会第一时间执行
  5. 然后 当 依赖的 数据被改变之后,当前 computed 的 watcher 的 dirty被置为 true
  6. 然后 如果 执行了 当前 computed 的 getter的话,就会 触发 上面的 computedGetter
  7. 执行 对应 watcher 的 evaluate 函数,获取 计算属性的值的同时,将 dirty 置为 false
  8. 这样 下次再去 获取 computed 的值的时候,就会 直接将缓存的值 返回,不再进行计算,这样就进行了 数据的缓存

14、侦听器的初始化


function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    // 如果 handler 是一个数组的话,遍历进行处理
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])   // 创建 用户的 watcher 对象
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  // 如果是一个对象的话,把 handler 取出来
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)  // 最后还是调用了 $watch 函数
}
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    // 如果传入的是对象,就重新解析
    const vm: Component = this
    if (isPlainObject(cb)) {  // 如果是个对象,再次 调用  
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    // 用户 watcher ,设置为 true
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {  // 是否立即执行
      try {
        // 当前监听属性的值传入了函数
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    // 返回了一个函数,可以取消监听
    return function unwatchFn () {
      watcher.teardown()
    }
  }
  1. 如果 handler 是一个数组的话,遍历进行处理

  2. 如果传入的 执行函数 是一个对象的话,把 handler 取出来

  3. 调用 vue.prototype.$watch

  4. 如果传入的 handler 是一个函数,再次调用 createWatcher (这里是因为 vue.prototype.$watch 是可以在全局 使用的函数,所以要额外做一次校验)

  5. 创建一个 watcher,同时传入 的参数之中将 user 设置为 true ,这里也就是为什么前面将 Watcher 分为 用户 watcher 和渲染 watcher 的原因

  6. 如果传入了 immediate 参数,将对应的 handler 函数执行一遍

  7. 最后返回 一个 unwatchfn,作用就是取消监听

 

 

15、首次渲染的过程

能看到这里的,对于首次渲染的过程肯定了清楚了

  1. 首先 执行 vue 实例的 _init 函数
  2. _init 函数给当前实例 注入 父子关系、响应事件、h 函数、执行 钩子函数、设置数据的响应式、注入provide
  3. 挂载 整个页面,如果传入 render 函数的话,获取 render 函数,如果没有的话,就 将 template 或者 el 的outerHTML 编译成 render 函数,同时 生成 当前组件的 渲染 Watcher ,将 render 函数 传入
  4. 渲染 Watcher 被创建的时候,就会开始执行 对应的 get ,也就是 render 函数
  5. 在 render 函数中,会获取当前 data 中的数据,这样就会触发 数据响应式中的 getter 函数,进行依赖收集
  6. 这样在以后的数据更新中,就会执行 对应的 渲染watcher 了 
  7. 至于 怎么更新的,以及挂载的,可以参考 vue 中 patch、patchVnode 函数(更新节点)createElm 函数 的学习
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值