Vue.js源码 生命周期 LifeCycle 学习

callHook
。 
我们来看看 callHook 代码:

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook] // 获取Vue选项中的生命周期钩子函数
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm) // 执行生命周期函数
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
}

  // src/core/instance/init.js
  Vue.prototype._init = function (options?: Object) {
    ……
    initLifecycle(vm) // 初始化生命周期
    initEvents(vm) // 初始化事件
    initRender(vm) // 初始化渲染
    callHook(vm, 'beforeCreate')
    initInjections(vm) // 初始化Inject
    initState(vm) // 初始化数据
    initProvide(vm) // 初始化Provide
    callHook(vm, 'created')
    ……
    if (vm.$options.el) {
      vm.$mount(vm.$options.el) // 如果有el属性,将内容挂载到el中去。
    }
  }

beforeMount & mounted
beforeMount 
在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。 
mounted 
el 被新创建的 vm.el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.el 也在文档内。

贴出代码逻辑// src/core/instance/lifecycle.js
// 挂载组件的方法

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount')

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

  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}


那么这个 mountComponent 在哪里用了呢?就是在Vue的 $mount 方法中使用。

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

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


最后会在Vue初始化的时候,判断是否有 el,如果有则执行 $mount 方法。

// src/core/instance/init.js

if (vm.$options.el) {
  vm.$mount(vm.$options.el) // 如果有el属性,将内容挂载到el中去。
}


至此生命周期逻辑应该是 beforeCreate - created - beforeMount -mounted

beforeUpdate & updated
beforeUpdate 
数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。 
updated 
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

找代码逻辑~ beforeUpdate 和 updated 在两个地方调用。

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    // 如果是已经挂载的,就触发beforeUpdate方法。
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    ……
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }



在执行 _update 方法的时候,如果 DOM 已经挂载了,则调用 beforeUpdate 方法。 
在 _update 方法的最后作者也注视了调用 updated hook 的位置:updated 钩子由 scheduler 调用来确保子组件在一个父组件的 update 钩子中。 
我们找到 scheduler,发现有个 callUpdateHooks 方法,该方法遍历了 watcher 数组。

// src/core/observer/scheduler.js

function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated')
    }
  }
}



这个 callUpdatedHooks 在 flushSchedulerQueue 方法中调用。

/**
 * 刷新队列并运行watcher
 */
function flushSchedulerQueue () {
  flushing = true
  let watcher, id
  queue.sort((a, b) => a.id - b.id)

  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    id = watcher.id
    has[id] = null
    watcher.run()
  }

  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // 调用组件的updated和activated生命周期
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
}



继续找下去

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true // 此参数用于判断watcher的ID是否存在
    ……
    if (!waiting) {
      waiting = true
      nextTick(flushSchedulerQueue)
    }
  }
}



最终在 watcher.js 找到 update 方法:

  // src/core/observer/watcher.js

  update () {
    // lazy 懒加载
    // sync 组件数据双向改变
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this) // 排队watcher
    }
  }


等于是队列执行完 Watcher 数组的 update 方法后调用了 updated 钩子函数。

beforeDestroy & destroyed
beforeDestroy 
实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。 
destroyed 
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

看代码~

  // src/core/instance/lifecycle.js
  // 销毁方法
  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      // 已经被销毁
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // 销毁过程
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let 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
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // 触发 destroyed 钩子
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
  }


这是一个销毁 Vue 实例的过程,将各种配置清空和移除。

activated & deactivated
activated 
keep-alive 组件激活时调用。 
deactivated 
keep-alive 组件停用时调用。

找到实现代码的地方

// src/core/instance/lifecycle.js
export function activateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = false
    if (isInInactiveTree(vm)) {
      return
    }
  } else if (vm._directInactive) {
    return
  }
  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, 'activated')
  }
}

export function deactivateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = true
    if (isInInactiveTree(vm)) {
      return
    }
  }
  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i])
    }
    callHook(vm, 'deactivated')
  }
}


以上两个方法关键就是修改了 vm._inactive 的值,并且乡下遍历子组件,最后触发钩子方法。

errorCaptured
当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

这是 2.5 以上版本有的一个钩子,用于处理错误。

// src/core/util/error.js
export function handleError (err: Error, vm: any, info: string) {
  if (vm) {
    let cur = vm
    // 向上冒泡遍历
    while ((cur = cur.$parent)) {
      // 获取钩子函数
      const hooks = cur.$options.errorCaptured
      if (hooks) {
        for (let i = 0; i < hooks.length; i++) {
          try {
            // 执行 errorCaptured 钩子函数
            const capture = hooks[i].call(cur, err, vm, info) === false
            if (capture) return
          } catch (e) {
            globalHandleError(e, cur, 'errorCaptured hook')
          }
        }
      }
    }
  }
  globalHandleError(err, vm, info)
}




代码很简单,看代码即可~

生命周期
除了生命周期钩子外,vue还提供了生命周期方法来直接调用。

vm.$mount
如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。 
如果没有提供 elementOrSelector 参数,模板将被渲染为文档之外的的元素,并且你必须使用原生 DOM API 把它插入文档中。 
这个方法返回实例自身,因而可以链式调用其它实例方法。

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  if (el === document.body || el === document.documentElement) {
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    // 获取template
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    // 编译template
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  // 执行 $mount 方法
  return mount.call(this, el, hydrating)
}



其实很简单,先获取html代码,然后执行 compileToFunctions 方法执行编译过程(具体编译过程在学习Render的时候再说)。

vm.$forceUpdate
迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

   Vue.prototype.$forceUpdate = function () {
    var vm = this;
    if (vm._watcher) {
      vm._watcher.update();
    }
  };



这是强制更新方法,执行了 vm._watcher.update() 方法。

vm.$nextTick
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。

找了找 vm.$nextTick 的代码

  // src/core/instance/render.js
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }



找到这个 nextTick 方法:

// src/core/util/next-tick.js
export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}



具体功能逻辑等学习完 render 再更新……

vm.$destroy
完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。 
触发 beforeDestroy 和 destroyed 的钩子。

关于$destroy 我们之前再说 destroyed 钩子的时候提到过了,这里就不再赘述。

  Vue.prototype.$destroy = function () {
    ……
  }
 

猜你喜欢

转载自blog.csdn.net/qq_34629352/article/details/83099078