初始化流程

new Vue

咱们在应用 Vue 的时候,首页就是先 new Vue(...) ;在上一章中通过剖析构建流程,咱们得出入口文件 src/platforms/web/entry-runtime-with-compiler.js ,通过入口文件,咱们一步一步找到 Vue 构造函数定义所在:

// src/platforms/web/entry-runtime-with-compiler.js// ...import Vue from './runtime/index'// ...
// src/platforms/web/runtime/index.jsimport Vue from 'core/index'// ...
// src/core/index.jsimport Vue from './instance/index'import { initGlobalAPI } from './global-api/index'import { isServerRendering } from 'core/util/env'import { FunctionalRenderContext } from 'core/vdom/create-functional-component'// 初始化全局 APIinitGlobalAPI(Vue)// ...
// src/core/instance/index.jsimport { initMixin } from './init'import { stateMixin } from './state'import { renderMixin } from './render'import { eventsMixin } from './events'import { lifecycleMixin } from './lifecycle'import { warn } from '../util/index'// Vue 构造函数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)/** * 定义: *   Vue.prototype.$data *   Vue.prototype.$props *   Vue.prototype.$set *   Vue.prototype.$delete *   Vue.prototype.$watch */stateMixin(Vue)/** * 定义 事件相干的 办法: *   Vue.prototype.$on *   Vue.prototype.$once *   Vue.prototype.$off *   Vue.prototype.$emit */eventsMixin(Vue)/** * 定义: *   Vue.prototype._update *   Vue.prototype.$forceUpdate *   Vue.prototype.$destroy */lifecycleMixin(Vue)/** * 定义: *   Vue.prototype.$nextTick *   Vue.prototype._render */renderMixin(Vue)export default Vue

_init

// src/core/instance/init.jsexport function initMixin (Vue: Class<Component>) {  Vue.prototype._init = function (options?: Object) {    const vm: Component = this    // 每个实例都保留一个 _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    vm._isVue = true    // 解决组件配置项    if (options && options._isComponent) {      // 每个子组件初始化时走这里,这里只做了一些性能优化      // 将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以进步代码的执行效率      initInternalComponent(vm, options)    } else {      // 合并选项,合并默认选项和自定义选项      vm.$options = mergeOptions(        resolveConstructorOptions(vm.constructor),        options || {},        vm      )    }    // 设置代理,将 vm 实例上的属性代理到 vm._renderProxy    /* istanbul ignore else */    if (process.env.NODE_ENV !== 'production') {      initProxy(vm)    } else {      vm._renderProxy = vm    }    // expose real self    vm._self = vm    // 初始化实例关系属性,$parent、$children、$refs、$root等    initLifecycle(vm)    // 初始化自定义事件,解决父组件传递的事件和回调    initEvents(vm)    // 解析组件的插槽信息,失去 vm.$slot,解决渲染函数(_render),失去 vm.$createElement 办法,即 h 函数    initRender(vm)    // 调用 beforeCreate 钩子函数    callHook(vm, 'beforeCreate')    // 初始化组件的 inject 配置项,失去 result[key] = val 模式的配置对象,而后对后果数据进行响应式解决,并代理每个 key 到 vm 实例    initInjections(vm)    // 数据响应式外围,解决 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)    }    if (vm.$options.el) {      vm.$mount(vm.$options.el)    }  }}

下面代码很清晰的看出初始化都做了哪些事件,在初始化的最初,如果有 el 属性,则会主动调用 vm.$mount 进行挂载,否则咱们就须要手动调用 $mount。接下里就进入了挂载阶段。

Vue 实例挂载

$mount

入口文件 src/platforms/web/entry-runtime-with-compiler.js

/* @flow */import config from 'core/config'import { warn, cached } from 'core/util/index'import { mark, measure } from 'core/util/perf'import Vue from './runtime/index'import { query } from './util/index'import { compileToFunctions } from './compiler/index'import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'const idToTemplate = cached(id => {  const el = query(id)  return el && el.innerHTML})/** * 编译器的入口 * 进行预编译,最终将模版编译成 render 函数 */// 缓存原型上的办法const mount = Vue.prototype.$mount// 从新定义该办法Vue.prototype.$mount = function (  el?: string | Element,  hydrating?: boolean): Component {  el = el && query(el)  // 不能挂载在 body、html 这样的根节点上  if (el === document.body || el === document.documentElement) {    process.env.NODE_ENV !== 'production' && warn(      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`    )    return this  }  const options = this.$options  /**   *   若没有 render办法,则解析 template 和 el,并转换为 render 函数   *   优先级:render > template > el   */  if (!options.render) {    let template = options.template    // template    if (template) {      if (typeof template === 'string') {        if (template.charAt(0) === '#') {          // { template: '#app' },以 id 为 ‘app’ 的节点,作为挂载节点          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 是一个失常的元素,获取其 innerHtml 作为模版        template = template.innerHTML      } else {        if (process.env.NODE_ENV !== 'production') {          warn('invalid template option:' + template, this)        }        return this      }    } else if (el) {      // el      template = getOuterHTML(el)    }    if (template) {      /* istanbul ignore if */      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {        mark('compile')      }      // 编译模版,失去动静渲染函数和动态渲染函数      const { render, staticRenderFns } = compileToFunctions(template, {        // 在非生产环境下,编译时记录标签属性在模版字符串中开始和完结的地位索引        outputSourceRange: process.env.NODE_ENV !== 'production',        shouldDecodeNewlines,        shouldDecodeNewlinesForHref,        // 界定符,默认 {{}}        delimiters: options.delimiters,        // 是否保留正文        comments: options.comments      }, this)      // 将两个渲染函数放到 this.$options 上      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')      }    }  }  // 调用原型上办法挂载  return mount.call(this, el, hydrating)}/** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */function getOuterHTML (el: Element): string {  if (el.outerHTML) {    return el.outerHTML  } else {    const container = document.createElement('div')    container.appendChild(el.cloneNode(true))    return container.innerHTML  }}Vue.compile = compileToFunctionsexport default Vue

从下面代码能够看出,不论定义 render 办法还是 eltemplate 属性,最终的目标就是失去 render 渲染函数。而后保留在 options 上。

编译模板,失去 render 渲染函数,通过调用 compileToFunctions 办法,这个到编译器的时候再一块看。

最初调用原型上的 $mount ,定义在 src/platform/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)}

理论调用 mountComponent ,定义在 src/core/instance/lifecycle.js

mountComponent

// src/core/instance/lifecycle.jsexport 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 {    // 执行 vm._render() 函数,失去 虚构 DOM,并将 vnode 传递给 _update 办法,接下来就该到 patch 阶段了    updateComponent = () => {      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  new Watcher(vm, updateComponent, noop, {    before () {      if (vm._isMounted && !vm._isDestroyed) {        callHook(vm, 'beforeUpdate')      }    }  }, true /* isRenderWatcher */)  hydrating = false  // vm.$vnode 示意 Vue 实例的父虚构 Node,所以它为 Null 则示意以后是根 Vue 的实例  if (vm.$vnode == null) {    vm._isMounted = true    callHook(vm, 'mounted')  }  return vm}

mountComponent 内定义了 updateComponent 办法,而后实例化一个Watcher,同时将 updateComponent 作为参数传入,在 Watcher 的回调函数中被调用。Watcher 在这里次要是初始化和数据变动时,执行回调函数。

最初设置 vm._isMounted = true ,示意实例已挂载。

updateComponent 的调用会执行 vm._updatevm._rendervm._render 获取虚构DOM,vm._update 更新视图。

下面代码呈现了三个生命周期钩子 beforeMountbeforeUpdatemounted ;也就是说,在执行 vm._render()  之前,执行了 beforeMount 钩子函数;在执行完 vm._update()  把虚构DOM转换实在 DOM 后,执行 mounted 钩子函数;后续若数据变动时,通过 _isMounted 标记,示意已挂载则执行 beforeUpdate 钩子函数。

这里值得注意的是,在 mounted 钩子执行前有个判断,只有在父虚构 Node 为 null 的时候执行。只有 new Vue 才会走到这里,如果是组件的话,它的父虚构 Node 是存在的。组件的 mounted 在别的中央。

相干链接

Vue(v2.6.14)源码解毒(预):手写一个简易版Vue

Vue(v2.6.14)源码解毒(一):筹备工作

Vue(v2.6.14)源码解毒(二):初始化和挂载

[Vue(v2.6.14)源码解毒(三):响应式原理(待续)]()

[Vue(v2.6.14)源码解毒(四):更新策略(待续)]()

[Vue(v2.6.14)源码解毒(五):render和VNode(待续)]()

[Vue(v2.6.14)源码解毒(六):update和patch(待续)]()

[Vue(v2.6.14)源码解毒(七):模板编译(待续)]()

如果感觉还对付的话,给个赞吧!!!也能够来我的集体博客逛逛 https://www.mingme.net/