Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue官网中的约束源码解释 -- 生命周期 #12

Open
muwoo opened this issue Apr 26, 2018 · 0 comments
Open

Vue官网中的约束源码解释 -- 生命周期 #12

muwoo opened this issue Apr 26, 2018 · 0 comments

Comments

@muwoo
Copy link
Owner

muwoo commented Apr 26, 2018

关于生命周期的源码执行

首先我们先来看一张官网的图:

然后我们来看一下源码里什么时候开始执行各个生命周期的:

1. beforeCreate、created

beforeCreatecreated钩子在core/instance/init.js_init方法中执行

initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
nitProvide(vm) // resolve provide after data/props
callHook(vm, 'created')

这里主要是初始化一些vm的属性,initState主要为定义的data属性进行obsever以及处理一些propswatchcomputed:

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

2. beforMounted

在执行beforMounted的钩子的时候,会进行几部判断:

1. 判断存不存在$el属性
  if (vm.$options.el) {
      vm.$mount(vm.$options.el)
  }
2. 判断存不存在template属性:
    let template = options.template
    let template = options.template
    if (template) {
      // string
      if (typeof template === 'string') {
        // 如果第一个字符是#,则 template = query(id).innerHTML
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
        }
     // dom 节点
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }

3. mounted

这一步主要是经过了 render --> VNode --> path步骤后生成了一个真实的dom节点,并挂载到el上:

return function patch () {
  ...
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  return vnode.elm
}
function invokeInsertHook (vnode, queue, initial) {
   ...
    for (let i = 0; i < queue.length; ++i) {
        queue[i].data.hook.insert(queue[i])
     }
 }

insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      // 这里执行 mounted
      callHook(componentInstance, 'mounted')
    }
 }

4. beforeUpdate

当我们执行dom更新之前,且已经经过mounted。会触发的钩子:

vm._update(vm._render(), hydrating)
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  ...
    if (vm._isMounted) {
       callHook(vm, 'beforeUpdate')
    }
  ...
}

5. updated

这个钩子函数主要是在异步更新队列中执行,也就是nextTick更新dom后会执行的钩子

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

function flushSchedulerQueue () {
   ...
   watcher.run()
  ...
  callUpdatedHooks(updatedQueue)
}

关于什么是nextTick?以及Event loop相关知识,有兴趣可以参考我的这两篇文章:

Vue nextTick 机制

Event loop 简介

6. beforeDestroy destroyed

$destroy函数被调用时,会首先触发beforeDestroy钩子:

 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)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

可以看到,destroy步骤如下:

  1. remove(parent.$children, vm)从父节点中先移除自己
  2. vm._watcher.teardown() 销毁watchers
  3. vm._data.__ob__.vmCount-- 从数据ob中删除引用
  4. vm.__patch__(vm._vnode, null) 调用当前渲染树上的销毁钩子
  5. callHook(vm, 'destroyed') 调用destroyed钩子
  6. vm.$off()销毁事件监听 ...

到这里差不多就执行完了销毁任务,从而触发了destroyed钩子

一些警告

不要在选项属性或回调上使用箭头函数,比如 created: () => console.log(this.a) 或 vm.$watch('a', newValue => this.myMethod())。因为箭头函数是和父级上下文绑定在一起的,this 不会是如你所预期的 Vue 实例,经常导致 Uncaught TypeError: Cannot read property of undefined Uncaught TypeError: this.myMethod is not a function 之类的错误。
我们可以看一下Vue是如何执行生命周期函数的:

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook]
  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)
  }
}

比如我们执行beforeCreate钩子:callHook(vm, 'beforeCreate')。因为是箭头函数,所以可以先了解箭头函数的几个特性:

  1. 箭头函数作为函数的一种形式,对于this的处理和普通函数有所区别,其没有自己的this上下文,也就是说通过bind/call/apply函数方法设置this值时无效的,会被忽略
  2. 因为箭头函数没有自己的this上下文,那么当它作为对象的方法函数运行时,this并不指向这个对象
  3. 箭头函数的的函数体中出现的this在运行时绑定到最近的作用域上下文对象
  4. 你可以认为箭头函数的this和调用者无关,只和其定义时所在的上下文相关

说到这里,应该明白了为什么不要在选项属性或回调上使用箭头函数了吧...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant