探究 vue 生命周期

前言

本来今天是想继续昨天的内容,深入源码探究插槽作用域的实现,但是由于篇幅过长还未能及时整理完善,所以先来补充一下前置知识:关于组件挂载时经历的生命周期。

大家都知道生命周期是 Vue 中的重要概念,每个 Vue 组件都有自己的生命周期,通过深入了解 Vue 的生命周期,能够更好地掌握 Vue 框架的工作原理和运行机制,进行高效地开发。

这里预警一下? 这又是一篇长文哦

希望你们能有足够的耐心看完,当然也可以选择感兴趣的部分进行阅读?

vue 的生命周期

组件生命周期经历的几个阶段:

  1. 数据初始化(beforeCreate、created) 为Vue实例上初始化一些属性,事件以及响应式数据
  1. 模板编译    created ==>> beforeMount 之间进行,  将模板编译成渲染函数
  1. 挂载阶段(beforeMount、mounted) 将模板渲染到真实的DOM中,挂载指定的DOM上
  1. 卸载阶段(beforeDestory、destoryed)将实例自身从父组件中删除,并取消依赖追踪及事件监听器
    数据更新时   beforeUpdate updated
    activated deactivated
    errorCaptured  在捕获一个来自后代组件的错误时被调用。

vue实例化

let vm = new Vue({})

执行了new Vue()之后,实际上是通过调用 _init 方法触发一系列的初始化流程。而_init方法是通过initMixin方法挂载到Vue原型中的。

src\core\instance\index.js

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')
  }

  // 调用 Vue.prototype._init 方法,该方法是在 initMixin 中定义的
  this._init(options)
}




// 定义 Vue.prototype._init 方法
initMixin(Vue)
// 定义了 $data、 $props、 $set、 $delete、$watch
stateMixin(Vue)
// 处理事件相关 $emit、$on、$off、$once
eventsMixin(Vue)
// _update   $forceUpdate  $destory
lifecycleMixin(Vue)
// $nextTick  _render  installRenderHelpers
renderMixin(Vue)

附:生命周期图解

数据初始化  _init()

beforeCreated: 此时 datael 均未初始值 undefined。 beforeCreated 是在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。此时的  options 中的 data 和 this 还未进行绑定。

beforeCreated  ===>> created期间:  实现 注入和响应式的初始化 initInject  initState initProvide

created: 实例创建并完成  数据观测(data observer)、data、 methods、 watch、event事件回调 ,options中的属性都绑定到了this实例对象上,并对属性都设置了响应式处理, created 是最早能获取数据的 hook。

initMixin:

Vue.prototype._init:

src\core\instance\init.js

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this

    // a uid 每个实例都有一个 uid 每实例化一个 uid + 1
    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
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      
      // 子组件的初始化
      initInternalComponent(vm, options)
    } else {
      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)  //   初始化组件实例关系属性,比如 $parent、$children、$root、$refs 等
      
    /**
     * 初始化自定义事件,这里需要注意一点,所以我们在 <comp @click="handleClick" /> 上注册的事件,监听者不是父组件,
     * 而是子组件本身,也就是说事件的派发和监听者都是子组件本身,和父组件无关
    */
    initEvents(vm)

    //  渲染初始化 初始化插槽  获取 this.$slots  定义 this._c 即 createElement 方法   平时使用 h 函数
    initRender(vm)





    // 执行 beforeCreate 生命周期钩子函数
    callHook(vm, 'beforeCreate')


    // 初始化组件的 inject 配置项,得到 result[key] = val 形式的配置对象,对结果数据进行响应式处理,并代理每个 key 到 vm 实例
    initInjections(vm)   // resolve injections before data/props

    // 数据响应式的重点,处理 props、methods、data、computed、watch
    initState(vm)


    // 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
    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)
    }

    // 如果发现配置项上有 el 选项,则自动调用 $mount 方法
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

由此可以初步得出数据初始化的顺序: beforeCreate ==> inject ==>> provide ==>> created

provide / inject 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

mergeOption:  整合、初始化部分属性

在接收到options之后,会先交给mergeOptions整合一遍部分属性,它负责按照预设定逻辑合并上级options(如果有)和规范prop,inject, Directives的命名。

并且它还会接受上级mixins(上级组件和全局)。合并两个选项,出现相同配置项时,子选项会覆盖父选项的配置。

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }






  if (typeof child === 'function') {
    child = child.options
  }




  // 选项的标准化处理  props、Inject、Directives 命名驼峰化
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)








  // 递归合并选项  合并过的选项会使用 _base 作为标记
  if (!child._base) {
    // extends 基于 组件扩展另一个组件
    if (child.extends) {
      // 通过地递归处理选项  如果是子组件则合并父组件项
      parent = mergeOptions(parent, child.extends, vm)
    }

    if (child.mixins) {
      // 把混入的复用项合并到实例中
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }



    }

  }


  const options = {}
  let key
  // 遍历父级所有的 option,如果和子组件的option有冲突则使用子组件的option
  for (key in parent) {
    mergeField(key)
  }
  // 遍历 子选项,如果父选项不存在该配置,则合并;否则跳过,因为父子拥有同一个属性的情况在上面处理父选项时已经处理过了,用的子选项的值
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }


  // 合并选项,childVal 优先级高于 parentVal
  function mergeField (key) {
    // strats = Object.create(null)
    const strat = strats[key] || defaultStrat
    // 值为如果 childVal 存在则优先使用 childVal,否则使用 parentVal
    options[key] = strat(parent[key], child[key], vm, key)
  }

  return options
}

// 从组件构造函数中解析配置对象 options,并合并基类选项
export function resolveConstructorOptions (Ctor: Class<Component>) {
  // 从实例构造函数上获取选项
  let options = Ctor.options
  if (Ctor.super) {
     // 存在基类,递归解析基类构造函数的选项
    const superOptions = resolveConstructorOptions(Ctor.super)
    // 缓存
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // 基类的配置项 与缓存中的不一致  说明 基类发生了更改
      Ctor.superOptions = superOptions
      // 找到更改的选项 去更新
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        // 如果存在被修改或增加的选项,则合并两个选项
        extend(Ctor.extendOptions, modifiedOptions)
      }

      //  将新增的选项赋值给 当前选项的 options
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }

    }

  }

  return options
}





// 解析构造函数选项中后续被修改或者增加的选项
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  const latest = Ctor.options
  const sealed = Ctor.sealedOptions
  // 对比两个选项,记录不一致的选项
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = latest[key]
    }


  }

  return modified
}

initLifecycle:   挂载默认属性

// 创建生命周期
export function initLifecycle (vm: Component) {
  const options = vm.$options



  // 找到父节点然后将当前元素和父节点进行关联
  let parent = options.parent
  //找到第一个非抽象父节点
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    // 为子组件时自行添加到本组件的 $chidren 数组中
    parent.$children.push(vm)
  }

    
//  $开头属性都是提供用户使用的,内部属性都是以下划线_;
  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm
  vm.$children = []
  vm.$refs = {}


  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

在 initLifecycle 中 初始化实例属性,对当前的 vue 的 parent 元素进行绑定, 并设置 $root 根属性, 初始化 _isMounted__isDestroyed 生命周期状态属性

initEvents: 初始化 v-on 注册事件

将父组件上注册的事件添加到子组件实例的 _events 对象中

export function initEvents (vm: Component) {
  vm._events = Object.create(null)  // 用于存储事件
  vm._hasHookEvent = false // 是否存在 hook 事件
  // 初始化父组件的监听事件
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)  // 将父组件向子组件注册的事件注册到子组件实例中的_events对象里
  }


}




let target: any
function add (event, fn) {
  target.$on(event, fn)  // 监听绑定事件
}




function remove (event, fn) {
  target.$off(event, fn)  // 解绑事件
}




function createOnceHandler (event, fn) {
  const _target = target
  // 创建只执行一次的 once 事件
  return function onceHandler () {
    const res = fn.apply(null, arguments)
    if (res !== null) {
      _target.$off(event, onceHandler)
    }




  }

}





export function updateComponentListeners (
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  target = vm
  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm  // 更新监听器
  target = undefined
}

updateListeners

src\core\vdom\helpers\update-listeners.js

export function updateListeners (
  on: Object,  // 父组件的事件监听对象
  oldOn: Object,  // 如果不是初次渲染, 原先的 vm 实例上可能存在一些原有的事件
  add: Function,
  remove: Function,
  createOnceHandler: Function,
  vm: Component
) {
  let name, def, cur, old, event
  for (name in on) {      // 遍历父组件的事件对象
    def = cur = on[name]  // 父组件中的事件
    old = oldOn[name]     // 当前组件中已存在的同名事件
    event = normalizeEvent(name)
    /* istanbul ignore if */
    if (__WEEX__ && isPlainObject(def)) {
      cur = def.handler
      event.params = def.params
    }



    if (isUndef(cur)) {   // 父组件事件不存在, 直接报警告
      process.env.NODE_ENV !== 'production' && warn(
        `Invalid handler for event "${event.name}": got ` + String(cur),
        vm
      )
    } else if (isUndef(old)) {   // 父组件事件存在, 并且当前组件不存在该事件的同名事件
      if (isUndef(cur.fns)) {   
        cur = on[name] = createFnInvoker(cur, vm)
      }
      if (isTrue(event.once)) {   // 如果是 once 修饰符修饰过的事件
        // 为当前组件绑定 once 类型事件
        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
      }



      // 将父组件的事件存入当前组件事件对象
      add(event.name, cur, event.capture, event.passive, event.params)
    } else if (cur !== old) {
      //  父组件的事件 与 当前组件的事件同名,但处理方式不同
      // 父组件存在该事件,子组件存在同名事件, 并且父子组件对于同一个事件的处理函数不相同
      // 则采用从父组件传递过来的处理函数
      old.fns = cur
      on[name] = old
    }


  }

  // 删除 vm 上之前存在、现在不存在的事件
  for (name in oldOn) {
    if (isUndef(on[name])) {
      event = normalizeEvent(name)
      remove(event.name, oldOn[name], event.capture)
    }

  }
}

初始化事件 实际上初始化的是父组件在模板中使用v-on或@注册的监听子组件内触发的事件。

initRender:  初始化渲染

  1. 将组件的插槽编译成虚拟节点 DOM 树, 以列表的形式挂载到 vm 实例,初始化作用域插槽为空对象;
  1. 将模板的编译函数(把模板编译成虚拟 DOM 树)挂载到 vm 的 _c 和 $createElement 属性;
  1. 最后把父组件传递过来的 attrsattrs 和 listeners 定义成响应式的。
export function initRender (vm: Component) {
  vm._vnode = null 
  vm._staticTrees = null 
  // 合并后的 option 赋值
  // 找到父组件节点 parentVnode  和渲染内容 renderContext
  const options = vm.$options
  const parentVnode = vm.$vnode = options._parentVnode 
  const renderContext = parentVnode && parentVnode.context




  // 初始化插槽  resolveSlots 找出当前组件下父组件传入的slot,并且返回;与子组件的插槽对应
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject




  // 将createElement fn绑定到这个实例,这样就能在里面获得合适的渲染环境。
  //  createElement 用来生成虚拟DOM  
  //  挂载到 vm 的 _c (从模板 <template> 编译得到的组件) 和 $createElement (用户使用 render 渲染写法) 两个属性上
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)




  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)



  const parentData = parentVnode && parentVnode.data

  // 把父组件的 $attrs 和 $listeners 用 defineReactive()设置为响应式
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
    }, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
      !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
    }, true)
  } else {
    defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
  }


}

转存失败,建议直接上传图片文件attrs,可直接获取根组件所传递参数(不包含classstyle和事件属性),而不用每一级组件逐层传递。

转存失败,建议直接上传图片文件listeners”`传入内部组件。

  • $listeners是组件的内置属性,它的值是父组件(不含.native修饰器的) v-on事件监听器
  • 组件可以通 过在自己的子组件上使用v-on=”$listeners”,进一步把值传给自己的子组件

callHook触发钩子函数

遍历数组,执行钩子函数

export function callHook (vm: Component, hook: string) {
  // 在执行生命周期钩子函数期间禁止依赖收集 
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  // handlers 数组  
  // 从组件的配置项中获取 mounted 生命周期钩子函数
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    // 执行生命周期函数
    for (let i = 0, j = handlers.length; i < j; i++) {
      //  通过 invokeWithErrorHandling 方法执行生命周期函数
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }

  }


  
  // Hook Event,如果设置了 Hook Event,比如 <comp @hook:mounted="method" />,则通过 $emit 触发该事件
  // vm._hasHookEvent 标识组件是否有 hook event,这是在 vm.$on 中处理组件自定义事件时设置的
  // 如果存在 HookEvent  执行插入的对应的函数
  if (vm._hasHookEvent) {
    //  通过 $emit 触发 hook: mounted 事件 
    // 执行 vm._events['hook:mounted'] 数组中的所有处理函数
    vm.$emit('hook:' + hook)
  }


  // 关闭依赖收集
  popTarget()
}











/**
 * 通用函数,执行指定函数 handler
 * 传递进来的函数会被用 try catch 包裹,进行异常捕获处理
 */
export function invokeWithErrorHandling (
  handler: Function,
  context: any,
  args: null | any[],
  vm: any,
  info: string
) {
  let res
  try {
    // 执行传递进来的函数 handler,并将执行结果返回
    res = args ? handler.apply(context, args) : handler.call(context)
    if (res && !res._isVue && isPromise(res) && !res._handled) {
      res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
      // issue #9511
      // avoid catch triggering multiple times when nested calls
      res._handled = true
    }
  } catch (e) {
    handleError(e, vm, info)
  }

  return res
}

vm.$on
/**

 * 监听实例上的自定义事件,vm._event = { eventName: [fn1, ...], ... }
 * @param {*} event 单个的事件名称或者有多个事件名组成的数组
 * @param {*} fn 当 event 被触发时执行的回调函数
 * @returns 
 */
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
  const vm: Component = this
  if (Array.isArray(event)) {
    // event 是有多个事件名组成的数组,则遍历这些事件,依次递归调用 $on
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$on(event[i], fn)
    }




  } else {
    // 将注册的事件和回调以键值对的形式存储到 vm._event 对象中 vm._event = { eventName: [fn1, ...] }
    (vm._events[event] || (vm._events[event] = [])).push(fn)
    // hookEvent,提供从外部为组件实例注入声明周期方法的机会
    // 比如从组件外部为组件的 mounted 方法注入额外的逻辑
    // 该能力是结合 callhook 方法实现的
    if (hookRE.test(event)) {
      vm._hasHookEvent = true
    }

  }
  return vm
}


每个生命周期钩子名称都对应了一个钩子函数数组。然后遍历该数组,将数组中的每个钩子函数都执行一遍。

在beforeCreate 之前完成了 属性、 事件、渲染 的初始化准备。

此后

beforeCreate  ===>> created 之间  依次执了initInjections、initState、initProvide,完成了依赖注入和响应式的初始化。

initInject:依赖注入

初始化 inject 配置项 :  向上级查找对应的provided

1、从祖代组件的配置中找到 对应的 provide ,配置项中每一个 key 对应的 val, 得到 result[key] = val

2、对结果数据进行响应式处理,代理每个 key 到 vm 实例

export function initInjections (vm: Component) {
  // 解析 inject 配置项,然后从祖代组件的配置中找到每个 provide 对应的值,配置项中每一个 key 对应的 val  最后得到 result[key] = val
  const result = resolveInject(vm.$options.inject, vm)
  if (result) {
    // 设置 shouldObserve 为 false, 不设置 Observe 观察  
    toggleObserving(false)
    // 对结果数据进行响应式处理, 当 shouldObserve 为 false 时,defineReactive 只是代理每个 key 到 vm 实例
    Object.keys(result).forEach(key => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
            `overwritten whenever the provided component re-renders. ` +
            `injection being mutated: "${key}"`,
            vm
          )
        })
      } else {
        defineReactive(vm, key, result[key])
      }
    })
    toggleObserving(true)
  }


}







//  解析 inject 配置项,从祖代组件的 provide 配置中找到 key 对应的值,否则用 默认值,最后得到 result[key] = val 
export function resolveInject (inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null)
    const keys = hasSymbol
      ? Reflect.ownKeys(inject)
      : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      if (key === '__ob__') continue
      // 获取 key 的 from 属性作为 provideKey
      const provideKey = inject[key].from
      let source = vm
      
       // 遍历所有的祖代组件,直到 根组件,找到组件中配置的 _provide 选项,从而找到对应 key 的值,
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey]
          break
        }

        source = source.$parent
      }
      //  如果上一个循环未找到,则采用 inject[key].default,
      // 如果没有设置 default 值,则抛出错误
      if (!source) {
        if ('default' in inject[key]) {
          const provideDefault = inject[key].default
          result[key] = typeof provideDefault === 'function'
            ? provideDefault.call(vm)
            : provideDefault
        } else if (process.env.NODE_ENV !== 'production') {
          warn(`Injection "${key}" not found`, vm)
        }
      }
    }

    return result
  }

inject 祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

inject 使用方式

祖代像所有子代组件透传数据

provide可以是一个对象,也可以是一个返回对象的函数。

inejct:可以是字符串数组或者一个对象。

// 1.父组件提供向子组件要传递的参数
provide() {
    return {
      listType: this.listType,
    }
  }

// 2.子组件使用:
inject: ['listType'],




// 或者在 inject 中指定你的默认值和你参数的来源:
inject:{
  listType:{
  from:"par"//provide定义的名字
  default:1
  }


}

inject 的响应式

provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

provide 里的值如果引用了 data 的数据,传的是基本类型, data 被改变了,inject 里的值是不会被改变的,因为在初始化的时候,没有监听数据。

如果需要父组件的数据改变了子组件的 inject 数据一起更新,需要传入引用类型,这样在 Object.defineProperty 的 get 时,当前 Dep.target 为 render 或其他的wacther,能够收集到引用的对象的值。

initProvide:初始化 provide

解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上

export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

inject选项接收到注入的值有可能被以上这些数据所使用到,所以在初始化完inject后需要先初始化这些数据,然后才能再初始化provide,所以在调用initInjections函数对inject初始化完之后需要先调用initState函数对数据进行初始化,最后再调用initProvide函数对provide进行初始化。

initState: 响应式

数据响应式的入口:分别处理 props、methods、data、computed、watch

优先级:props、methods、data、computed 对象中的属性不能出现重复,优先级和列出顺序一致

其中 computed 中的 key 不能和 props、data 中的 key 重复,methods 不影响

//响应式原理入口
export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // 处理 props 对象,为 props 对象的每个属性设置响应式,并将其代理到 vm 实例上
  if (opts.props) initProps(vm, opts.props)
  // 处理 methos 对象,校验每个属性的值是否为函数、和 props 属性比对进行判重处理,最后得到 vm[key] = methods[key]
  if (opts.methods) initMethods(vm, opts.methods)
  
  /**

   *   1、判重处理,data 对象上的属性不能和 props、methods 对象上的属性相同
   *   2、代理 data 对象上的属性到 vm 实例
   *   3、为 data 对象的上数据设置响应式 
   */
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }

   /** 
  * computed 缓存实现原理  本质是通过 watcher 实现 
  *  1、对每个 computed[Key] 创建 watcher 实例 默认懒执行
  *  2、将 computed[Key] 代理到 vue 实例上  支持通过 this.computedKey 的方式访问 computed.key
  *  3、判重 computed 中的 key 不能和 data、props 中的属性重复
  */
  if (opts.computed) initComputed(vm, opts.computed)


 /**  
   *  1、处理 watch 对象
   *  2、为 每个 watch.key 创建 watcher 实例,key 和 watcher 实例可能是 一对多 的关系
   *  3、如果设置了 immediate,则立即执行 回调函数 
   */
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

proxy

将属性代理到实力上的方式

// 设置代理,将 key 代理到 target 上
export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }


  Object.defineProperty(target, key, sharedPropertyDefinition)
}
initProps

处理 props 对象,为 props 对象的每个属性设置响应式,并将其代理到 vm 实例上

function initProps (vm: Component, propsOptions: Object) {
  const propsData = vm.$options.propsData || {}
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array instead of dynamic object key enumeration.
  // 缓存 props 的每个 key 用数组进行迭代,性能优化
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  if (!isRoot) {
    toggleObserving(false)
  }
  // 遍历 props 对象
  for (const key in propsOptions) {
    // 缓存 key
    keys.push(key)
    // 获取 props[key] 的默认值
    const value = validateProp(key, propsOptions, propsData, vm)
    
    // 为 props 的每个 key 设置数据响应式  将 key 代理到 props 上
    defineReactive(props, key, value)
    
    // static props are already proxied on the component's prototype
    // during Vue.extend(). We only need to proxy props defined at
    // instantiation here.
    if (!(key in vm)) {
      // 代理 key 到 vm 对象上
      proxy(vm, `_props`, key)
    }

  }
  toggleObserving(true)
}

initMethods
  1. 校验 methoss[key],必须是一个函数
  1. 判重

    • methods 中的 key 不能和 props 中的 key 相同

    • methos 中的 key 与 Vue 实例上已有的方法重叠,一般是一些内置方法,比如以 $ 和 _ 开头的方法

  2. 将 methods[key] 放到 vm 实例上,得到 vm[key] = methods[key]

function initMethods (vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (process.env.NODE_ENV !== 'production') {
      // 遍历多有的 methods 校验 methoss[key],必须是一个函数
      if (typeof methods[key] !== 'function') {
        warn(
          `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
          )
      }


      // methods 中的 key 不能和 props 中的 key 相同
      if (props && hasOwn(props, key)) {
        warn(

          `Method "${key}" has already been defined as a prop.`,
          vm
          )
      }

      // methos 中的 key 与 Vue 实例上已有的方法重叠
      if ((key in vm) && isReserved(key)) {
        warn(

          `Method "${key}" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
          )
      }


    }




    // 将 methods[key] 放到 vm 实例上,得到 vm[key] = methods[key]
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
  }
}

initData
  1. 判重处理,data 对象上的属性不能和 props、methods 对象上的属性相同
  1. 代理 data 对象上的属性到 vm 实例
  1. 为 data 对象的上数据设置响应式
function initData (vm: Component) {
  // 得到 data 对象
  let data = vm.$options.data
  // 保证后续处理的 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
    )
  }


  // 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') {
      // 不能与 methods 同名
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }



    }

    // 不能与 props 同名
    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)) {
      // 将data 对象上的属性到 vm 实例
      proxy(vm, `_data`, key)
    }

  }
  // observe中  通过defineReactive()设置响应式, 实现了数据的响应式处理 
  observe(data, true /* asRootData */)
}









export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}
initComputed
  1. 为 computed[key] 创建 watcher 实例
  1. 代理 computed[key] 到 vm 实例
  1. 判重,computed 中的 key 不能和 data、props 中的属性重复
const computedWatcherOptions = { lazy: true }


function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()
  // 遍历 computed 对象
  for (const key in computed) {
    // 获取 key 对应的值,即 getter 函数
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(

        `Getter is missing for computed property "${key}".`,
        vm
      )
    }







    if (!isSSR) {
      // create internal watcher for the computed property.
      //  为 computed 属性创建 watcher 实例
      watchers[key] = new Watcher(    //watcher的取值函数就是我们在computed中定义的函数
        vm,
        getter || noop,
        noop,
        //  computed 配置项  懒执行
        computedWatcherOptions   // { lazy: true }
      )
    } 
    if (!(key in vm)) {
      // 代理 computed 对象中的属性到 vm 实例
      // 这样就可以使用 vm.computedKey 访问计算属性了
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      // 非生产环境有一个判重处理,computed 对象中的属性不能和 data、props 中的属性相同
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      } else if (vm.$options.methods && key in vm.$options.methods) {
        warn(`The computed property "${key}" is already defined as a method.`, vm)
      }

    }

  }
}


/**
 * 代理 computed 对象中的 key 到 target(vm)上
 */
export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    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) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  // 将 computed 配置项中的 key 代理到  vue 实例上,
  // 支持通过 this.computedKey 的方式访问 computed 中的属性
  // 拦截对 target.key 的访问和设置
  Object.defineProperty(target, key, sharedPropertyDefinition)
}


function createComputedGetter (key) {
   // computed 属性值会缓存的原理也是在这里结合 watcher.dirty、watcher.evalaute、watcher.update 实现的
  return function computedGetter () {   //当视图对newMsg进行取值的时候会执行这里

  // 得到当前 key 对应的 watcher
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // 执行 watcher.evaluate()
      // 执行 computed.key 的值(函数) 得到函数的执行结果  赋值给 watcher.value
      // 将 watcher.dirty 值为 false
      // 待页面更新后 执行wathcer.update() 将 watcher.dirty 重新置为 true,


      // computed 和 watcher 的区别
      // computed 实现原理: 一次渲染中只执行一次 computed 函数  后续访问是不在执行,
      // 直到下一次更新之后再次执行 computed 函数
   
      //  watcher.dirty 值为 false 时 不执行 evaluate 直接从 watcher.value 中拿值
      if (watcher.dirty) {   //这里要对照Watcher的构造函数来看,默认watcher.dirty = watcher.lazy,首次执行为true
        watcher.evaluate()  //会执行watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

function createGetterInvoker(fn) {
  return function computedGetter () {
    return fn.call(this, this)
  }
}

initWatch

function initWatch (vm: Component, watch: Object) {
  // 遍历 watch 配置项
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      // handler 为数组,遍历数组,获取其中的每一项,然后调用 createWatcher
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }

  }
}




/**
 * 两件事:
 *   1、兼容性处理,保证 handler 肯定是一个函数
 *   2、调用 $watch 
 * @returns 
 */
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }


  if (typeof handler === 'string') {
    handler = vm[handler]
  }


  return vm.$watch(expOrFn, handler, options)
}

stateMixin
export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  //  处理 data 数据  定义 get 方法  访问 this._data
  const dataDef = {}
  dataDef.get = function () { return this._data }
  // 处理 prop 数据
  const propsDef = {}
  propsDef.get = function () { return this._props }
  //  异常提示
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(

        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }

    //  设置时  会提示 props 只读
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }

  }



  // 将 $data 和 $props 挂载到 Vue原型链上  支持通过 this.$data 和 this.$props 的方法访问
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)




 // $set 和 $delete
 // 是 全局API  set 和 del 的别名
  Vue.prototype.$set = set
  Vue.prototype.$delete = del


 // this.$watch
/**
 * 创建 watcher,返回 unwatch,共完成如下 5 件事:
 *   1、兼容性处理,保证最后 new Watcher 时的 cb 为函数
 *   2、标记用户 watcher
 *   3、创建 watcher 实例
 *   4、如果设置了 immediate,则立即执行一次 cb
 *   5、返回 unwatch 事件 用于解除 watch 监听
 * @param {*} expOrFn key 或者是 function
 * @param {*} cb 回调函数
 * @param {*} options 配置项,用户直接调用 this.$watch 时可能会传递一个 配置项
 * @returns 返回 unwatch 函数,用于取消 watch 监听
 */





  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    // 兼容性处理,因为用户调用 vm.$watch 时设置的 cb 可能是对象
    // 处理 cb 是对象的情况   保证后续处理的 cb 是一个函数
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }


    options = options || {}
    // 标记 这是一个 用户 watcher  
    options.user = true
    // 实例化 watcher 
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // 存在 immediate 为 true  则立即执行回调函数
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }

    // 返回 unwatcnFn  用于解除 watcher 监听
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

由 initState 可得知 数据初始化的顺序为

beforeCreate ==> inject ==>> props ==>> methods ==>> data ==>> computed ==>> watch ==>> provide ==>> created

在 initData 中实现了数据的响应式

小结

在created的之前initInjections,initProvide,initState(这里面初始化了一些常用的属性,

其中initData里面体现了init reactivity ,所以beforeCreate之前相当于在为后面的生命周期进行做准备,

所以在created之后就会完成一些常用属性的初始化,以及data数据的响应式绑定。也就是说在created之后就可以在vue中用this.data调用data里面的数据了。

  • 完成数据观测,属性与方法的运算,watch、event事件回调的配置
  • 可调用methods中的方法,访问和修改data数据触发响应式渲染dom,通过computed和watch完成数据计算
  • 但此时vm.$el 并没有被创建

new vue 期间所经历的函数

模板编译

created ==>> beforeMount期间:  实现了模板编译,生成模板字符串且并未挂载

  • 判断是否存在el选项,若不存在则停止编译,直到调用vm.$mount(el)才会继续编译
  • vm.el获取到的是挂载DOM的HTML
  • 优先级:render > template > outerHTML

initMixin   created之后则是进入了模板编译阶段

// 如果发现配置项上有 el 选项,则自动调用 $mount 方法,
// 反之,没有 el 则必须手动调用 $mount
// el 选项为 vue实例挂载的目标
    if (vm.$options.el) {
      // 调用 $mount 方法,进入编译阶段
      vm.$mount(vm.$options.el)
    }

runtime-compile $mount函数

src\platforms\web\entry-runtime-with-compiler.js

//  初始化最后一步的$mount  做备份
const mount = Vue.prototype.$mount
// 覆写 $mount
Vue.prototype.$mount = function (
  // el挂载点 
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 得到挂载点
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      // 不能挂载到html或者body元素上 
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }






  // 配置选项
  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        // id选择器
        if (template.charAt(0) === '#') {
          // 通过idToTemplate  获得el的innerHTML
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      //获取outerHTML 
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
        
        
      // compileToFunctions 的实现 返回 render() 
      // 将模板编译成 ast 语法树
      // 将 AST 进行一个优化,其实主要优化是打上了静态节点的标记
      // 将编译好的 AST 对象,转化为字符串。,这个字符串将在渲染函数中用来生成 Vnode
      const { render, staticRenderFns } = compileToFunctions(template, {
        // 标记元素在HTML  模板字符串中的开始和结束索引位置
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        // 定界符  {{}}
        delimiters: options.delimiters,
        // 编译时是否保留注释内容
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }


  // 存在render直接执行备份的$mount方法执行挂载
  return mount.call(this, el, hydrating)
}

runtime compile 它缓存了原型的$mount方法,再重写了这个方法,在里面加入了编译生成render的部分。runtime-compile版本,会判断是否有tempalte,如果有就用compileToFunctions函数将其编译成render函数,如果没有template就将el挂载的外部html编译成render函数。

compile版本的$mount函数中对 tempalt 进行判断,再通过 compileToFunctions对tempalte 进行处理,将其编译成render函数。

runtime-compiler:template ==>>ast(abstract syntax tree:抽象语法数) ==>> render函数 ==>> vdom(virtual 虚拟dom) ==>> 真实DOM

原型的 $mount (runtime only)

src\platforms\web\runtime\index.js

// 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: 组件的挂载

src\core\instance\lifecycle.js

// $mount 调用

export function mountComponent (

  vm: Component,

  el: ?Element,

  hydrating?: boolean

): Component {

  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }

  }
  // beforeMount
  callHook(vm, 'beforeMount')






  let updateComponent

  /* istanbul ignore if */

  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)

      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 = () => {
      // 执行_update 进入更新阶段  首先执行 _render 将组件变成 VNode
      vm._update(vm._render(), hydrating)
    }
  }




  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

runtime-compile main.js 配置

import Vue from "vue/dist/vue.esm.js";   //修改引入的源文件

console.log(`Vue`, Vue);

import App from "./App.vue";

import router from "./router";

import store from "./store";

 

import ElementUI from "element-ui";

import "element-ui/lib/theme-chalk/index.css";

 

Vue.config.productionTip = false;

Vue.use(ElementUI);

 

new Vue({

  router,

  store,

  components: { App },   //组件的注册
  template: "<App/>",    //组件的使用
}).$mount("#app");

runtime-only main.js 配置

import Vue from "vue/dist/vue.esm.js";   //修改引入的源文件

console.log(`Vue`, Vue);

import App from "./App.vue";

import router from "./router";

import store from "./store";

 

import ElementUI from "element-ui";

import "element-ui/lib/theme-chalk/index.css";

 

Vue.config.productionTip = false;

Vue.use(ElementUI);

 

new Vue({

  router,

  store,

  render: (h) => h(App), 
}).$mount("#app");

runtime compiler和runtime only的区别

  1. main.js 的不同 :在complier的main.js文件中,Vue实例内部需要先注册组件,然后设置模板;而在only的main.js文件中,直接用render函数代替了这两个过程。
  1. runtime-compiler:template ==>>ast(abstract syntax tree:抽象语法数) ==>> render函数 ==>> vdom(virtual 虚拟dom) ==>> 真实DOM
    runtime-only:render函数 ==> vdom(virtual 虚拟dom) ==> 真实DOM, 不能解析template 模板,只能通过  vue-template-compiler 将 template 转化为 render函数。(npm安装vue-template-compiler,)
    因此
    runtime-only  性能更高,因为不需要 ,template ==>> ast(abstract syntax tree:抽象语法数)这两步

挂载阶段:

beforeMount:在模板编译阶段 template通过compileToFunctions() 生成了 render() , 但是还未生成虚拟DOM

beforeMount ==>> mounted 期间:做好渲染准备了之后,执行 vm._render生成虚拟DOM,然后再通过vm._update渲染成真实的DOM

mounted: 虚拟Dom已经被挂载到真实Dom上,此时我们可以获取Dom节点,$ref在此时也是可以访问的。

beforeMount:

src\core\instance\lifecycle.js   mountComponent() 中

// $mount 调用

export function mountComponent (

  vm: Component,

  el: ?Element,

  hydrating?: boolean

): Component {

  // 挂载点
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(

          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(

          'Failed to mount component: template or render function not defined.',
          vm
        )
      }


    }




  }

  callHook(vm, 'beforeMount')






  let updateComponent

  /* istanbul ignore if */

  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)

      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 = () => {
      // 执行_update 进入更新阶段  
      // _update 中主要功能是调用patch,
      //  1. 首先执行 _render 将组件变成 VNode ,
      //  2. 再将vnode转换为真实DOM,并且更新到页面中
      vm._update(vm._render(), hydrating)
    }
  }



  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  // 实例化 watcher 时会执行 this.get()   
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // vm.$vnode 为空,则为根实例,即渲染完毕,开始进入 mounted 阶段
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

mounted:

src\core\instance\lifecycle.js  mountComponent 后半段

// beforeMount
callHook(vm, 'beforeMount')
...



let updateComponent
  /* istanbul ignore if */
  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)
      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 = () => {
      // 执行_update 进入更新阶段  
      // 首先执行 _render 生成 VNode,最终调用 vm._update 更新 成真实DOM
      vm._update(vm._render(), hydrating)
    }

  }




  // 实例化 watcher 时主动触发 watcher.get() 
  // 通过 updateComponent 回调(_render)实现页面的更新 
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }


  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  // vm.$vnode 为空,则为根实例,即渲染完毕,开始进入 mounted 阶段
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm

实例化 一个 watcher 在初始化时调用 this.get() 方法 触发 updateComponent() 回调 ,然后观察到vm中数据发生变化的时候再执行updateComponent()更新DOM。

updateComponent 中通过 vm._render 方法先生成虚拟DOM,最终调用 vm._update 更新 成真实DOM

updateComponent = () => {
	vm._update(vm._render(), hydrating)
}

vm._render()

render的作用主要是生成vnode

// 定义vue 原型上的render方法
Vue.prototype._render = function (): VNode {
    const vm: Component = this

    // render函数来自于组件的option
    const { render, _parentVnode } = vm.$options



    if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
            _parentVnode.data.scopedSlots,
            vm.$slots,
            vm.$scopedSlots
        )
    }






    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
        // There's no need to maintain a stack because all render fns are called
        // separately from one another. Nested component's render fns are called
        // when parent component is patched.
        currentRenderingInstance = vm
        // 调用render方法,自己的独特的render方法, 传入createElement参数,生成vNode
        vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
        handleError(e, vm, `render`)
        // return error render result,
        // or previous vnode to prevent render error causing blank component
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
            try {
                vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
            } catch (e) {
                handleError(e, vm, `renderError`)
                vnode = vm._vnode
            }
        } else {
            vnode = vm._vnode
        }

    } finally {
        currentRenderingInstance = null
    }

    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
        if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
            warn(
                'Multiple root nodes returned from render function. Render function ' +
                'should return a single root node.',
                vm
            )
        }
        vnode = createEmptyVNode()
    }


    // set parent
    vnode.parent = _parentVnode
    return vnode
}

render() 是在created ===>> beforeMoun 之间

由template通过 compileToFunctions() 生成的,而 render 调用的参数 vm.createElement  是在 beforeCreate 初始化准备工作 initRender()  把 createElement 挂载在vm上的 _c 和  createElement 属性上的,而 vm._c 是在模板编辑成的 render 中使用,vm.createElement是在用户手写的 render 中使用,所以真正创建虚拟DOM的其实是 createElement 函数创建了一个VNode Tree

vm._update 触发的时机

  1. DOM的初次渲染
  1. 数据发生变化的时候更新DOM
    vm._update 把 虚拟DOM转换成真实的DOM

小结

mounted之后就代表着渲染的完成,已经真实的DOM已经挂载到html上面,此时可以进行DOM的相关操作,在beforeMount和mounted函数之间,vue调用了render,将模板字符串转化成虚拟DOM

更新阶段:(响应式)

数据的更新随即进入了响应式原理的实现

beforeUpdate: 在数据发生改变后,DOM 被更新之前被调用。此时数据已更新,页面未更新 date 与 view暂未同步

beforeUpdate ==>> updated期间: 虚拟DOM 重新渲染并更新应用(date ==>> view) 的更新

updated:  数据与页面保持同步

Watcher:通过 watcher 实现数据的更新从而触发页面的更新

渲染watcher 在 vm.$mount() 执行时创建 ,传入 updateComponent 回调,调用 this.getter() 时触发 updateComponent 进行页面的更新, this.before() 触发beforeUpdate 生命周期

//   初次渲染  以及  更新组件 
updateComponent = () => {
    // 执行_update 进入更新阶段  首先执行 _render 将组件变成 VNode
    vm._update(vm._render(), hydrating)
}



// init  render watcher
new Watcher(vm, updateComponent, noop, {
    before: function before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate');
      }


    }




  }, true /* isRenderWatcher */);

Vue的_update方法是实例上的一个私有方法,主要作用是把VNode渲染成真实的dom,它在首次渲染和数据更新的时候被调用。在数据更新的时候会发生VNode和旧VNode对比,获取差异更新视图,我们常说的diff就是发生在此过程中。

_update

src\core\instance\lifecycle.js

  // 组件初次渲染和更新的入口
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    // 页面挂载点   真实的元素
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    // preNode 表示旧的节点
    if (!prevVnode) {
      // initial render (首次渲染)
      // patch 阶段 patch  diff算法
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {

      // updates  更新节点  
      vm.$el = vm.__patch__(prevVnode, vnode)
    }

    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }




    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }


vm.__patch__

__patch__ 进入 patch 算法,主要是将vnode转换成dom,渲染在视图中, vm.__patch__原型上挂载的位置:

  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树

/src/platforms/web/runtime/index.js

// 在 Vue 原型链上安装 web 平台的 patch 函数
Vue.prototype.__patch__ = inBrowser ? patch : noop
// 所以对于客户端来说真实执行的是patch,其中noop表示空函数,如果不在客户端的话,就都是在服务端,所以不需要渲染真实得dom。



//  patch import from   src\platforms\web\runtime\patch.js
// patch 工厂函数,为其传入平台特有的一些操作,然后返回一个 patch 函数
export const patch: Function = createPatchFunction({ nodeOps, modules })

craetePatchFunction

其中包括 patch 执行过程中的工具方法,最终将 patch() 方法返回

src\core\vdom\patch.js

const hooks = ['create', 'activate', 'update', 'remove', 'destroy']


/**
 * 工厂函数,注入平台特有的一些功能操作,并定义一些方法,然后返回 patch 函数
 */
export function createPatchFunction (backend) {
  let i, j
  const cbs = {}




  /**

   * modules: { ref, directives, 平台特有的一些操纵,比如 attr、class、style 等 }
   * nodeOps: { 对真实 DOM 元素的增删改查 API }
   */
  const { modules, nodeOps } = backend



  /**
   * hooks = ['create', 'activate', 'update', 'remove', 'destroy']
   * 遍历这些钩子,然后从 modules 的各个模块中找到相应的方法,比如:directives 中的 create、update、destroy 方法
   * 让这些方法放到 cb[hook] = [hook 方法] 中,比如: cb.create = [fn1, fn2, ...]
   * 然后在合适的时间调用相应的钩子方法完成对应的操作
   */
  for (i = 0; i < hooks.length; ++i) {
    // 比如 cbs.create = []
    cbs[hooks[i]] = []
    for (j = 0; j < modules.length; ++j) {
      if (isDef(modules[j][hooks[i]])) {
        // 遍历各个 modules,找出各个 module 中的 create 方法,然后添加到 cbs.create 数组中
        cbs[hooks[i]].push(modules[j][hooks[i]])
      }
    }

  }


  /**
   * vm.__patch__
   *   1、新节点不存在,老节点存在,调用 destroy,销毁老节点
   *   2、如果 oldVnode 是真实元素,则表示首次渲染,创建新节点,并插入 body,然后移除老节点
   *   3、如果 oldVnode 不是真实元素,则表示更新阶段,执行 patchVnode
   */
  return patch
}

patch

实现 diff 算法的入口函数,通过比较新旧节点来判断节点的操作

  1. 新节点不存在,老节点存在,调用 destroy,销毁老节点
  1. 新节点存在,老节点不存在,首次渲染组件
  1. 如果 oldVnode 不是真实元素,则表示更新阶段,执行 patchVnode
  1. 如果 oldVnode 是真实元素,则替换已存在的节点

    • 创建新节点 createElm()

    • 递归更新父的占位符节点

    • 删除旧节点 removeVnodes()

比较新旧节点的不同:  采用替换节点的方式

新旧节点节点相同的情况:采用 Diff算法 进行比较

src\core\vdom\patch.js

 return function patch (oldVnode, vnode, hydrating, removeOnly) {
    // 新节点不存在,老节点存在  则销毁老节点
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []



    if (isUndef(oldVnode)) {
      // 新节点存在,老节点不存在,首次渲染组件时
      // empty mount (likely as component), create new root element
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue)
    } else {

      //  判断 oldVNode 是否是一个真实元素
      const isRealElement = isDef(oldVnode.nodeType)
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // oldVNode 不是真实元素  而且 oldVNode 和 VNode 是同一个节点  则进行patch 更新节点
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
      } else {
        // oldVNode 是真实节点 则需要替换已存在节点
        // replacing existing element
       /**     
        *  替换已存在的节点
        *  1. 创建新节点 -- 以当前就节点为参考节点,通过 creatElm() 创建新节点并插入到Dom 中
        *  2. 更新父的占位符节点
        *  3. 删除旧节点 
       */


        if (isRealElement) {
          // 挂载到真实元素以及处理服务端渲染的情况
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              )
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          // 基于 oldVNode 是一个真实节点创建的 vnode
          oldVnode = emptyNodeAt(oldVnode)
        }

        // 获取节点的真实元素
        const oldElm = oldVnode.elm
        // 获取节点的父节点  即body
        const parentElm = nodeOps.parentNode(oldElm)

        // create new node
        // 基于新 vnode 创建整棵 DOM 树并插入到 body 元素下
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

        // update parent placeholder node element, recursively
        // 递归更新父占位符节点元素
        if (isDef(vnode.parent)) {
          let ancestor = vnode.parent
          const patchable = isPatchable(vnode)
          while (ancestor) {
            for (let i = 0; i < cbs.destroy.length; ++i) {
              cbs.destroy[i](ancestor)
            }
            ancestor.elm = vnode.elm
            if (patchable) {
              for (let i = 0; i < cbs.create.length; ++i) {
                cbs.create[i](emptyNode, ancestor)
              }
              // #6513
              // invoke insert hooks that may have been merged by create hooks.
              // e.g. for directives that uses the "inserted" hook.
              const insert = ancestor.data.hook.insert
              if (insert.merged) {
                // start at index 1 to avoid re-invoking component mounted hook
                for (let i = 1; i < insert.fns.length; i++) {
                  insert.fns[i]()
                }
              }
            } else {
              registerRef(ancestor)
            }
            ancestor = ancestor.parent
          }
        }

        // destroy old node
        
        // 删除旧节点
        if (isDef(parentElm)) {
          // 把 oldVnode 从当前 DOM 树中删除,如果父节点存在,则执行 removeVnodes 方法:
          removeVnodes([oldVnode], 0, 0)
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
createElm():创建新节点

在 patch 节点比较的过程中,发现有新的节点则调用 createElm() 创建真实的 dom 节点 并插入到父节点上

  //  创建真实的 dom 节点
  function createElm (
    vnode,

    insertedVnodeQueue,

    parentElm,
    refElm,
    nested,
    ownerArray,
    index
  ) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // This vnode was used in a previous render!
      // now it's used as a new node, overwriting its elm would cause
      // potential patch errors down the road when it's used as an insertion
      // reference node. Instead, we clone the node on-demand before creating
      // associated DOM element for it.
      vnode = ownerArray[index] = cloneVNode(vnode)
    }







    vnode.isRootInsert = !nested // for transition enter check
    // 重点
    // 用于创建创建组件 
    // 1. 如果 vnode 是一个组件,则创建  则执行 data.hook.init() 实例化组件并挂载
    //    为组件执行各个模块的 create 钩子函数
    // 2. 如果是创建普通元素 则什么也不做
    
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }






    // 获取data 对象
    const data = vnode.data
    // 所有子节点
    const children = vnode.children
    // 节点标签
    const tag = vnode.tag
    if (isDef(tag)) {
      if (process.env.NODE_ENV !== 'production') {
        if (data && data.pre) {
          creatingElmInVPre++
        }

        // 节点不存在异常提示
        if (isUnknownElement(vnode, creatingElmInVPre)) {
          warn(
            'Unknown custom element: <' + tag + '> - did you ' +
            'register the component correctly? For recursive components, ' +
            'make sure to provide the "name" option.',
            vnode.context
          )
        }

      }


      // 创建 DOM 节点
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)


      /* istanbul ignore if */
      if (__WEEX__) {
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
        if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
        createChildren(vnode, children, insertedVnodeQueue)
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
      } else {
        // 递归创建所有子节点   生成整颗 DOM 树
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        // 插入父节点
        insert(parentElm, vnode.elm, refElm)
      }

      if (process.env.NODE_ENV !== 'production' && data && data.pre) {
        creatingElmInVPre--
      }
    } else if (isTrue(vnode.isComment)) {
      // 创建注释节点 并  插入父节点
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      // 创建文本节点  并 插入父节点
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
  }
createComponent(): 创建组件

如果 vnode 是一个组件,则执行 init 钩子,创建组件实例,并挂载,为组件执行各个模块的 create 方法

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      // 判断  组件实例是否已经存在  &&  组件被 keep-alive 包裹
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      //  执行组件的 data.hook.init()   实例化组件 挂载
      // 执行 vnode.data.init 钩子函数,该函数在讲 render helper 时讲过
      // 如果是被 keep-alive 包裹的组件:则再执行 prepatch 钩子,用 vnode 上的各个属性更新 oldVnode 上的相关属性
      // 如果是组件没有被 keep-alive 包裹或者首次渲染,则初始化组件,并进入挂载阶段
      if (isDef(i = i.hook) && isDef(i = i.init)) {
          // 1. 调用组件vnode.data.hook上的init方法,执行了子组件构造函数的_init函数,并且执行了子组件的$mount方法。
          i(vnode, false /* hydrating */)
      }
      
       
      if (isDef(vnode.componentInstance)) {
        //  为组件执行各个模块的 create 钩子函数
        initComponent(vnode, insertedVnodeQueue)
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }

    }

  }

sameVnode

判断两个节点是否相同,

  • 如果两个 vnode 的 key 是否相同
  • 继续判断对于同步组件,则判断 isComment 注释节点、data 属性、input输入框类型  是否相同
  • 判断异步组件 (asyncFactory) 是否相同
/**

 * 判读两个节点是否相同 
 */
function sameVnode (a, b) {
  return (
    // key 必须相同,需要注意的是 undefined === undefined => true
    a.key === b.key && (
      (
        // 标签相同
        a.tag === b.tag &&
        // 都是注释节点
        a.isComment === b.isComment &&
        // 都有 data 属性
        isDef(a.data) === isDef(b.data) &&
        // input 标签的情况
        sameInputType(a, b)
      ) || (
        // 异步占位符节点
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}


removeVnodes: 删除旧节点

oldVnode 从当前 DOM 树中删除,如果父节点存在,则执行 removeVnodes 方法:

遍历待删除的 vnode 做删除,

  1. 调用removeAndInvokeRemoveHook() 从 dom 中移除节点并执行 remove 钩子函数,并对它的子节点递归调用 removeAndInvokeRemoveHook();
  1. invokeDestroyHook()执行 module 的 destory 钩子函数以及 vnodedestory 钩子函数,并对它的子 vnode 递归调用 invokeDestroyHook 函数;
  1. 最后执行removeNode() 调用平台的 dom API 将真正的节点移除
// 移除指定索引范围(startIdx —— endIdx)内的节点 
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
  for (; startIdx <= endIdx; ++startIdx) {
    const ch = vnodes[startIdx]
    if (isDef(ch)) {
      if (isDef(ch.tag)) {
        removeAndInvokeRemoveHook(ch)
        invokeDestroyHook(ch)
      } else { // Text node
        // 调用平台的 DOM API 去把真正的 DOM 节点移除
        removeNode(ch.elm)
      }


    }




  }

}


//  removeAndInvokeRemoveHook
// 从 dom 中移除节点并执行 remove 钩子函数,并对它的子节点递归调用 removeAndInvokeRemoveHook
  function removeAndInvokeRemoveHook (vnode, rm) {
    if (isDef(rm) || isDef(vnode.data)) {
      let i
      const listeners = cbs.remove.length + 1
      if (isDef(rm)) {
        // we have a recursively passed down rm callback
        // increase the listeners count
        rm.listeners += listeners
      } else {
        // directly removing
        rm = createRmCb(vnode.elm, listeners)
      }



      // recursively invoke hooks on child component root node
      if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
        removeAndInvokeRemoveHook(i, rm)
      }
      for (i = 0; i < cbs.remove.length; ++i) {
        cbs.remove[i](vnode, rm)
      }
      if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
        i(vnode, rm)
      } else {
        rm()
      }

    } else {
      removeNode(vnode.elm)
    }
  }











  //  销毁老节点
  // 执行组件的 destroy 钩子,即执行 $destroy 方法 
  // 执行组件各个模块(style、class、directive 等)的 destroy 方法
  // 如果 vnode 还存在子节点,则递归调用 invokeDestroyHook

  function invokeDestroyHook (vnode) {
    let i, j
    // 获取 data 系列
    const data = vnode.data
    if (isDef(data)) {
      // 执行 data.hook.destroy 钩子
      if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
      for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
    }

    // 递归的销毁所有子节点
    if (isDef(i = vnode.children)) {
      for (j = 0; j < vnode.children.length; ++j) {
        invokeDestroyHook(vnode.children[j])
      }
    }

  }
patchVnode:  新旧节点相同

在新旧节点相同的情况下,比较新旧节点的孩子节点,在对比两个虚拟节点过程中去更新真实dom

更新节点

  • 全量的属性更新再去比较所有的孩子节点
  • 如果新旧节点都有孩子,则递归执行 diff
  • 如果新节点有孩子,旧节点没孩子,则新增新节点的这些孩子节点
  • 如果旧节点有孩子,新节点没孩子,则删除旧节点的这些孩子
  • 更新文本节点
  function patchVnode (
    oldVnode,
    vnode,

    insertedVnodeQueue,

    ownerArray,
    index,
    removeOnly
  ) {
    // 老节点和新节点相同,直接返回
    if (oldVnode === vnode) {
      return
    }





    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // clone reused vnode
      vnode = ownerArray[index] = cloneVNode(vnode)
    }




    const elm = vnode.elm = oldVnode.elm



    if (isTrue(oldVnode.isAsyncPlaceholder)) {
      if (isDef(vnode.asyncFactory.resolved)) {
        hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
      } else {
        vnode.isAsyncPlaceholder = true
      }


      return
    }





     // 跳过静态节点的更新
    if (isTrue(vnode.isStatic) &&
      isTrue(oldVnode.isStatic) &&
      vnode.key === oldVnode.key &&
      (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
    ) {
      vnode.componentInstance = oldVnode.componentInstance
      return
    }



    let i
    const data = vnode.data

    // 执行 prepatch 钩子函数
    // 当更新的 vnode 是一个组件 vnode 的时候,会执行 prepatch 的方法
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      i(oldVnode, vnode)
    }






    // 获取新老节点的所有孩子节点  Vue 3.0 在这里做了很多的优化
    const oldCh = oldVnode.children
    const ch = vnode.children


    // 比较新旧节点的所有孩子节点
    // 全量更新节点的所有属性   执行 update 钩子函数
    //  在执行完新的 vnode 的 prepatch 钩子函数,会执行所有 module 的 update 钩子函数以及用户自定义 update 钩子函数
    if (isDef(data) && isPatchable(vnode)) {
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
      if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode)
    }





    // 判断 vnode 是否为文本节点
    /* 
    *   1. 新旧节点为文本节点 且新旧节点的文本内容不同时  直接替换文本内容 更新文本节点
    *   2. 不是文本节点则判断子节点:
    *      1. oldCh 和 ch 都存在且不相同时,通过 updateChildren (diff 算法)来更新子节点 
    *      2. 只有 ch 时,即新增新节点,删除旧节点,如果旧的节点是文本节点则先将节点的文本清除,
    *         再通过 addVnodes 创建这些新孩子节点批量插入到新节点的 elm 下
    *      3. 只有 oldCh 存在时,即移除旧节点,则通过 removeVnodes 清空旧节点
    *      4. 只有旧节点是文本节点时:旧节点的文本存在  新节点的文本不存在  则清空文本
    */
    if (isUndef(vnode.text)) {
      // 不是文本节点
      if (isDef(oldCh) && isDef(ch)) {
        //  如果新旧节点都有孩子,且孩子不相同  则递归执行 diff 过程
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        
        // 旧孩子不存在,新孩子存在,  表示新增节点   则创建这些新孩子节点
        if (process.env.NODE_ENV !== 'production') {
          checkDuplicateKeys(ch)
        }
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        // 创建元素
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        // 旧孩子存在,新孩子不存在,则移除这些旧孩子节点
        removeVnodes(oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        // 旧节点的文本存在  新节点的文本不存在  则清空文本
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      // 新旧节点都是文本节点  且文本发生了变化   则直接替换文本内容,更新文本节点
      nodeOps.setTextContent(elm, vnode.text)
    }
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

小结

数据更新是通过观察者对象的实例方法 run 完成的,从代码中可以看到:在数据更新前会调用实例对象上的 before 方法,从而执行 beforeUpdate 生命周期钩子函数;在数据更新完成后,通过执行 callUpdatedHooks 函数完成 updated 生命周期函数的调用。

数据更新过程中的流程图

销毁阶段:

vue会将自身从父组件中删除,取消实例上所有依赖的追踪并且移除所有的事件监听器

beforeDestory: 进入销毁阶段,实例身上的数据仍可使用

beforeDestory ==>> destoryed: 卸载 watchers、 data、 将组件从已经渲染的DOM中卸载

destoryed: 组件完全销毁,取消实例上所有依赖的追踪并且移除所有的事件监听器

// $destory 完全销毁一个实例, 触发 beforeDestory、destoryed 钩子
//  清理它与其他实例的连接,接触她的全部指令及事件监听器
Vue.prototype.$destroy = function () {
    var vm = this;
    if (vm._isBeingDestroyed) {
      return
    }

    callHook(vm, 'beforeDestroy');
    vm._isBeingDestroyed = true;
    // 将自身从父组件中删除
    var parent = vm.$parent;
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm);
    }

    // 卸载 wather
    if (vm._watcher) {
      vm._watcher.teardown();
    }



    var i = vm._watchers.length;
    while (i--) {
      vm._watchers[i].teardown();
    }

    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--;
    }




    // call the last hook...
    vm._isDestroyed = true;
    // 执行一次null的pathc将虚拟dom已经渲染的dom卸载
    vm.__patch__(vm._vnode, null);
    // fire destroyed hook
    callHook(vm, 'destroyed');
    // 执行destory钩子函数,执行off卸载,绑定事件,解除与el的绑定
    vm.$off();
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null;
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null;
    }

  };

小结

在 beforeDestory 进入实例的销毁阶段,但此时还能获取实例生的数据、事件,之后进入卸载阶段,首先 将自身从父组件上移除开始卸载 watcher、组件上的 data 数据 、卸载子组件的DOM ,在 destoryed 时子实例已被销毁,数据不可使用,移除事件监听器,父组件赋值为null。

完结

今天的生命周期就先到这里啦,能看到这里的同学们都很棒哦?

希望你们也能从中有所收获,由于篇幅有限,生命周期相关的问题我也会在后续更新

敬请期待哦。。。

参考

  1. 生命周期源码解读 :juejin.cn/post/699989…
  2. Vue2.0源码阅读笔记:juejin.cn/post/684490…
  3. initInject的实现 :juejin.cn/post/687778…
  4. 响应式原理、watcher的实现:zhuanlan.zhihu.com/p/168768245
  5. vue的响应式原理: juejin.cn/post/691827…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYu3Cwgl' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片