从vue2.6.11源码解读vue实例化过程

1.vue2.6.11文件结构

   vue文件结构下的详情:

distvue本身构建生成的文件
src
--compiler编译器
  --codegen键盘事件生成
     --event.js声明键盘事件和在Weex上生成具有绑定参数的处理程序代码
     --index.jscodegen入口文件,结合ASTElemen、键盘事件状态、插槽作用域确定规范(涉及:

sub-trees、v-once、v-if、v-for、key、pre、component、props、event handlers、v-model、inline-template、v-bind、v-on、data:finction、directives等等

)并生成渲染code

ASTElement

   --directives指令
      --bind.jsv-bind语法糖的实现(结合ASTElement和ASTDirective)ast详解
      --index.js

1.暴露v-bind和v-model语法糖出去

2.引入并暴露shared/util.js 的noop方法(不执行任何操作,不留下无用的传输代码,严格检查函数调用 Arity

      --model.jsv-model语法糖的实现(结合ASTElement和ASTModifiers)ast详解
      --on.jsv-on语法糖的实现(结合ASTElement和ASTDirective)ast详解
  --parser解析器

    --entity-decoder.js通过创建div标签来解码传入的html内容
    --filter-parser.js过滤函数解析(filter允许用在两个地方,一个是双括号插值,一个是v-bind表达式后面,如果解析到这两种情况,执行parseFilters解析filter)解析思路
    --html-parser.jshtml解析器不检查此文件的类型,因为它主要是供应商代码。
     --index.jsparser入口文件,主要结合entity-decoder.js、filter-parser.js和text-parser.js生成ASTElement树
     --text-parser.js将text里的文本内容 和 {{}}插值内容一并存入TextParseResult类型的对象中并返回, 插值里的内容先进行过滤器解析
  --codeframe.jscode生成模板方法
  --create-compiler.js创建编译器
  --error-detector.js错误检测器(定义错误规则并检测)
  --helpers.js编译助手(给el添加、绑定属性、指令)
  --index.js创建编译器生成ast树且渲染
  --optimizer.js优化器

/目标:遍历生成的模板AST树

并检测纯静态的子树,即

永远不需要改变的DOM。

一旦我们检测到这些子树:

1.将它们提升为常数,这样我们就不再需要在每次重新渲染时为其创建新节点;

2、在修补过程中完全跳过它们。

  --to-function.js编译器将模板转换成函数
--core核心实现
  --components核心组件
     --index.jscomponents入口文件,引入KeepAlive并导出
     --keep-alive.jsKeepAlive(缓存组件)的实现
  --global-api核心接口
     --assets.jsVue组件资源创建资源注册方法
     --extend.jsVue.extend()扩展接口

每个实例构造函数(包括Vue)都有一个唯一的cid。这使我们能够创建包装的“子对象”构造函数”,并缓存它们。

对于PROP和计算属性,我们在上定义代理getter扩展时的Vue实例,在扩展原型上。为创建的每个实例调defineProperty。

允许进一步扩展/混合/插件使用

创建资产寄存器,以便扩展类

也可以拥有他们的私人资源。

启用递归自查找,在扩展时保留引用。在实例化时,可以检查Super的选项是否已更新。并缓存构造函数

     --index.js全局api入口文件
     --mixin.jsVue.mixin()合并接口通过调用../util/index中的mergeOptions来实现
     --use.jsVue.use()插件接口通过调用../util/index中的

toArray来实现

  --instance核心实例
    --render-helpers渲染助手
      --bind-dynamic-keys.js帮助处理v-bind和v-on中动态参数的动态键将vue模板(如:

<div id="app" :[key]="value">

)编译(如:

_c('div', { attrs: bindDynamicKeys({ "id": "app" }, [key, value]) })

       --bind-object-listeners.js对象侦听器处理v-on=’{}'到vnode对象 data上
      --bind-object-props.js

用于将v-bind=“object”合并到VNode数据中的运行时助手

      --check-keycodes.js

用于从配置中检查键代码的运行时帮助程序。作为Vue.prototype公开,将eventKeyName作为最后一个参数单独传递给向后兼容。

      --index.jsrender-helpers(入口文件)
      --render-list.js用于呈现v-for列表的运行时助手
      --render-slot.js插槽渲染助手
      --render-static.js渲染静态树运行助手
      --resolve-filter.jsfilters过滤器渲染助手
      --resolve-scoped-slots.js同resolve-slots.js作用一样,不同的是编译父组件模板时,会生成一个返回结果为VNode的函数。当子组件匹配到父组件传递作用域插槽函数时,调用该函数生成对应VNode
      --resolve-slots.js将children VNodes解析为插槽对象运行助手
    --events.js核心事件($on、$off、$emit、$once)接口通过'../vdom/helpers/index'的

updateListeners监听来实现

    --index.js核心实例(入口文件)接口:(导入)代理,状态,渲染,事件,生命周期,基础工具
    --init.js封装vue初始化(实例化)init接口
    --inject.jsprovide和inject接口的实现
    --lifecycle.js生命周期接口的实现
    --proxy.js核心代理接口(判断平台是否支持Proxy功能;注册Proxy的处理函数;初始化代理initProxy() 注册vm._renderProxy属性)
    --render.js核心渲染接口
    --state.js核心状态接口(初始化顺序:props属性,methods属性,data属性,computed属性watch属性)
  --observer监视器接口
    --array.js数组监控

数组方法:'push','pop','shift',

'unshift','splice',sort','reverse'

    --dep.js消息订阅器
    --index.js数据监控器observer
    --scheduler.js事件(队列)侦听器
    --traverse.js对象侦听

递归遍历对象以调用所有已转换的getter,使对象内的每个嵌套属性,作为“深度”依赖项收集。

    --watcher.js消息订阅

观察者解析表达式,收集依赖项,并在表达式值更改时激发回调。这用于$watch()api和指令。

  --util核心工具
    --debug.jsdebug工具
    --env.js环境工具
    --error.js错误工具(在处理错误程序时,停用deps跟踪避免无限渲染)
    --index.js入口文件(导入工具接口)
    --lang.js语言扩展工具
    --next-tick.jsnextTick工具微任务异步延迟包装器,nextTick行为利用了可以访问的微任务队列;添加空计时器“强制”刷新微任务队列;策略

Promise、MutationObserver、setImmediate、setTimeout

    --options.js选项覆盖(父值和子值的合并策略及转换为最终结果)
    --perf.js忽略工具
    --props.jsprops属性工具
  --vdom
    --helpers助手
      --extract-props.jsprop提取助手
      --get-first-component-child.js获取第一个组件
      --index.js入口文件
      --is-async-placeholder.js异步占位符判断
      --merge-hook.js合并钩子
      --normalize-children.js规范化子项
      --normalize-scoped-slots.js规范化作用域插槽
      --resolve-async-component.js解析异步组件
      --update-listeners.js更新侦听器
    --modules模块
      --directives.js指令
      --index.js入口文件
      --ref.jsref
    --create-component.js提供createComponent方法创建组件返回vnode
    --create-element.js提供createElement方法
    --create-functional-component.js

提供createFunctionalComponent方法

    --patch.js基于Snabbdom的虚拟DOM修补算法
    --vnode.js入口文件(提供VNode类)
  --config.js核心配置文件
  --index.js核心入口文件,引入和导出vue api接口
--platforms应用平台
  --webweb平台
    --compiler编译器
    --directives指令
      --html.js添加html元素及内容
      --index.js入口文件
      --model.jsmodel指令
      --text.jstext指令
    --modules模块
      --class.jsdom节点样式class解析
      --index.js入口文件
      --model.js表单v-model解析
      --style.jsdom节点样式style解析
      --index.js入口文件
      --options.js编译器选项
      --util.js标签及属性判断归类方法
    --runtime运行
      --components组件
        --transition-group.js提供列表项支持
        --transition.js提供单个元素/组件支持
    --directives指令
       --index.js入口文件
       --model.jsmodel指令
       --show.jsshow指令
    --modules模块
      --attrs.jsattrs属性
      --class.jsclass属性
      --dom-props.jsdom的props属性
      --events.js事件
      --index.js入口文件
      --style.js样式优先级判断及更新
      --transition.jsdom节点的过渡
    --class-util.js封装添加/移除与SVG兼容的样式class
    --index.js入口文件
    --node-ops.jsnode节点的方法集成封装
    --patch.js内置模块(最后应用指令模块)
    --transition-util.js过渡集成封装
    --server服务端
       --directives指令
       --index.js入口文件
       --model.jsmodel指令
       --show.jsshow指令
    --modules模块
       --attrs.jsattrs属性
       --class.jsclass属性
       --dom-props.jsdom的props属性
       --index.js入口文件
       --style.js样式优先级判断及更新
      --compiler.js重命名并暴露编译器方法
      --util.js标签属性及样式属性判断归类方法
    --util应用
      --attrs.jsweb保留的attrs属性编译处理
      --class.js样式class编译处理
      --compat.js检查当前浏览器是否在属性值内编码字符
      --element.jshtml dom元素编译处理
      --index.js入口文件
      --style.js样式style元素编译处理
    --entry-compiler.js条目编译器
   --entry-runtime-with-compiler.js带编译器的入口运行
    --entry-runtime.js编译器运行入口文件
    --entry-server-basic-renderer.js服务端基本渲染器
    --entry-server-renderer.js服务端渲染器
  --weexweex平台
    --compiler
      --directives
        --index.js入口文件
        --model.jsmodel指令
      --modules模块
        --recycle-list回收列表
          --component-root.js根组件
          --component.js组件
          --index.js入口文件
          --recycle-list.js回收列表
           --text.js文本解析器
          --v-bind.jsv-bind解析器
          --v-for.jsv-for解析器
          --v-if.jsv-if解析器
          --v-on.jsv-on解析器
          --v-once.jsv-once解析器
      --append.js追加
      --class.js样式class解析器
      --index.js入口文件
      --props.jsprops解析器
      --style.js样式style解析器
     --index.js入口文件
   --runtime
     --components
       --index.js入口文件
       --richtext.js富文本
       --transition-group.js过渡组
       --transition.js过渡
    --directives指令
      --index.js入口文件
    --modules模块
      --attrs.jsattrs属性编译处理
      --class.js样式class编译处理
      --event.js事件
      --index.js入口文件
      --style.js样式style元素编译处理
      --transition.js过渡编译处理
    --recycle-list回收列表
      --render-component-template.js渲染组件模板
      --virtual-component.js虚拟组建
    --index.js        入口文件
    --node-ops.jsnode节点的方法集成封装
    --patch.js内置模块(最后应用指令模块)
    --node-ops.js节点文本
   --util应用
      --element.jshtml dom元素编译处理
      --index.js入口文件
      --parser.js解析器
    --entry-compiler.js条目编译器
    --entry-framework.js入口框架
    --entry-runtime-factory.js函数构建包装用于为每个Weex实例生成Vue的新副本
--server支持服务器端渲染,所有服务器端渲染相关的逻辑
  --bundle-renderer
    --create-bundle-renderer.js创建结束渲染器
    --create-bundle-runner.js创建结束运行程序
    --source-map-support.js源地图支持
  --optimizing-compiler
    --codegen.js通过扩展默认codegen来进行SSR优化节点
    --index.js                        入口文件
    --modules.js模块
    --optimizer.js

在SSR中,vdom树只生成一次,从不修补,因此我们可以将大多数元素/树优化为纯字符串渲染函数。

*SSR优化器遍历AST树以检测可优化的元素和树。SSR优化的标准比静态树要宽松一些检测(设计用于客户端重新渲染)。在SSR中,我们只为组件/插槽/自定义指令。

    --runtime-helpers.js运行帮助程序
  --template-renderer
    --create-async-file-mapper.js创建异步映射器

创建映射器,映射服务器端渲染期间使用的组件在客户端构建中异步区块文件,以便我们可以内联它们直接在呈现的HTML中,以避免瀑布式请求。

    --index.js入口文件
    --parse-template.js分析模板
    --template-stream.js模板流
  --webpack-pluginwebpack插件
    --client.js客户端
    --server.js服务端
    --util.js应用
  --create-basic-renderer.js创建基本渲染器
  --create-renderer.js 创建渲染器
  --render-context.js渲染上下文
  --render-stream.js渲染流
  --render.js服务器渲染函数
  --util.js判断js、css文件并创建promise回调
  --write.js创建写入函数(会判断堆栈)
--sfcsingle-file components(单文件组件)简单说:转换单文件组件(*.vue) 解析成一个javascript对象
    --parser.js主要内容:将单个文件组件(*.vue)文件解析为SFC描述对象。
--shared共享
   --constants.js常量:数据渲染,组件,指令,过滤器,生命周期,错误机制和SSR服务端渲染(组件在服务器上呈现之前解析,异步)
  --util.js封装了些简单实用明确的js方法(检查是是否为js原始值,undefined判断,对象判断,两个值是否大致相等,值在数组中的第一个索引,等)
types一些ts文件
LICENSE作者尤雨溪及版权声明
package.jsonvue2配置文件
README.mdvue的一些赞助商

2.vue实例化过程

vue构造函数,源码位置:src\core\instance\index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

/**
 * 
 * @param {*} options 是用户传递过来的配置项,如data、methods等常用的方法
 */
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')
  }
  this._init(options)
}
// 定义 _init
initMixin(Vue)
// 定义 $set $get $delete $watch 等
stateMixin(Vue)
// 定义事件$on  $once $off $emit
eventsMixin(Vue)
// 定义 _update  $forceUpdate  $destroy
lifecycleMixin(Vue)
// 定义 _render 返回虚拟dom
renderMixin(Vue)

export default Vue

initMixin方法,源码位置:src\core\instance\init.js

/* @flow */

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

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

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

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    // 合并属性,判断初始化的是否是组件,这里合并主要是 mixins 或 extends 的方法
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      //优化内部组件实例化,因为动态选项合并相当慢,而且内部组件选项需要特殊处理。
      initInternalComponent(vm, options)
    } else {
      // 合并vue属性
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      // 初始化proxy拦截器
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化组件生命周期标志位
    initLifecycle(vm)
    // 初始化组件事件侦听
    initEvents(vm)
    // 初始化渲染方法
    initRender(vm)
    callHook(vm, 'beforeCreate')
    // 初始化依赖注入内容,在初始化data、props之前
    initInjections(vm) // resolve injections before data/props
    // 初始化props/data/method/watch/methods
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    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)
    }
  }
}

结论:可以看出,

1)在调用beforeCreate之前,数据初始化并未完成,像dataprops这些属性无法访问到

2)到了created的时候,数据已经初始化完成,能够访问dataprops这些属性,但这时候并未完成dom的挂载,因此无法访问到dom元素

3)挂载方法是调用vm.$mount方法

initState方法,完成props/methods/data/computed/watch的初始化,源码位置:src\core\instance\state.js

export function initState(vm: Component) {
  // 初始化组件的watcher列表
  vm._watchers = []
  const opts = vm.$options
  // 初始化props属性
  if (opts.props) initProps(vm, opts.props)
  // 初始化methods方法
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化data函数/对象
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // 初始化computed方法
  if (opts.computed) initComputed(vm, opts.computed)
  // 初始化watch 方法
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

结论: 可以看出,初始化顺序为:

props => methods => data => computed => watch

可以根据这个初始化顺序,我们可以定义一些策略,如:data直接调用methods方法,computed调用methods方法,而不会产生undefine

initData方法,源码位置:src\core\instance\state.js

function initData (vm: Component) {
  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--) {
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') {
      // 属性名不能与方法名重复
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        )
      }
    }
    // 属性名不能与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)) { // 验证key值的合法性
      // 将_data中的数据挂载到组件vm上,这样就可以通过this.xxx访问到组件上的数据
      proxy(vm, `_data`, key)
    }
  }

结论:可以看出,

1)data定义的时候可选择函数形式或者对象形式(组件只能为函数形式)

2)data的属性名会在methods方法和props属性中判断是否重名

3)data会被响应式监听数据变化

vm.$mount挂载方法,源码位置:

web平台:src/platforms/web/runtime/index.js

weex平台:src/platform/weex/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

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  // 如果没有获取解析的render函数,则会抛出警告
  // render是解析模板文件生成的
  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 = () => {
      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

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  //手动装载实例,调用自行装载
  //在其插入的挂钩中为渲染创建的子组件调用mounted
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

结论:可以看出,

1) 会触发beforeCreate钩子
2) 定义updateComponent渲染页面视图的方法
3) 监听组件数据,一旦发生变化,触发beforeUpdate生命钩子
4) updateComponent方法主要执行在vue初始化时声明的render,update方法

render方法的主要作用是生成vnode, 源码位置:src/core/instance/render.js

// 定义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
  }

_update方法通过调用patch,将vnode转换为真实DOM,并且更新到页面中;源码位置:

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.
    if (!prevVnode) {
      // initial render
      // 执行具体的挂载逻辑
      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.
  }

$mount在web平台具体实现(src/platform/web/entry-runtime-with-compiler.js)

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 获取或查询元素
  el = el && query(el)

  /* istanbul ignore if */
  // vue 不允许直接挂载到body或页面文档上
  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
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    // 存在template模板,解析vue模板文件
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          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) {
      // 通过选择器获取元素内容
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      // 将temmplate解析ast tree, 将ast tree转换成render语法字符串, 生成render方法
      const { render, staticRenderFns } = compileToFunctions(template, {
        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')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

结论:可以看出,

1)不要将根元素放到body或者html上
2)可以在对象中定义template/render或者直接使用template、el表示元素选择器
3)最终都会解析成render函数,调用compileToFunctions,会将template解析成render函数
4)template解析步骤:
    将html文档片段解析成ast描述符
    将ast描述符解析成字符串
    生成render函数
    生成render函数,挂载到vm上后,会再次调用mount方法

initLifecycle方法初始化一些生命周期相关属性,源码位置:src/core/instance/lifecycle.js

export function initLifecycle(vm: Component) {
  // 初始化参数$options  
  const options = vm.$options

  // locate first non-abstract parent
  /**
   * 定位第一个“非抽象”的父组件
   * 抽象组件:<keep-alive>包裹动态组件时,会缓存不活动的组件实例,而不销毁。和<transition>相似
   * <keep-alive>是一个抽象组件:它自身不会渲染一个dom元素,也不会出现在父组件链中
   */
  let parent = options.parent
  if (parent && !options.abstract) {
    // 持续找组件的父组件直到找到非抽象组件
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }
  // 指定创建的实例的父实例,两者之间建立父子关系
  vm.$parent = parent
  // 当前组件树的根Vue实例;如果当前实例没有父实例,此实例将会是自己
  vm.$root = parent ? parent.$root : vm
  // 当前实例的直接子组件。但并不保证顺序,也不响应式
  vm.$children = []
  // 注册过ref的所有子组件
  vm.$refs = {}
  // 当前实例组件的watcher实例对象
  vm._watcher = null
  // keep-alive组件状态
  vm._inactive = null
  // keep-alive组件状态的属性
  vm._directInactive = false
  // 当前实例是否完成挂载
  vm._isMounted = false
  // 当前实例是否已被销毁
  vm._isDestroyed = false
  // 当前实例是否正在被销毁
  vm._isBeingDestroyed = false
}

结论:可以看出,

1)确定父子组件关系

2)初始化ref,watcher,ref属性,判断组件是否挂载、是否销毁和销毁中

stateMixin方法,源码位置:src/core/instance/state.js

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.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  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
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

结论:可以看出,通过定义dataDef和propsDef,来设置响应式data和data和props的get和set方法,并给vue实例添加set、delete和watch方法

eventsMixin方法:源码位置:src/core/instance/events.js

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

结论:可以看出,定义实现Vue实例的$on、$once、$off和$emit方法

lifecycleMixin方法,源码位置:src/core/instance/lifecycle.js

export function lifecycleMixin (Vue: Class<Component>) {
  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.
    if (!prevVnode) {
      // initial render
      // 执行具体的挂载逻辑
      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.
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

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

结论:可以看出,定义实现vue实例的_update,$forceUpdate,$destroy方法

renderMixin方法,源码位置:src/core/instance/render.js

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // 安装运行助手
  installRenderHelpers(Vue.prototype)
  // 定义$nextTick
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  // 定义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.
    // 设置父vnode。这允许渲染函数具有访问权限到占位符节点上的数据。
    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
  }
}

结论:可以看出,定义实现vue实例的 _render 并返回返回虚拟dom

错误处理handleError方法,源码地址:src/core/util/error.js

export function handleError (err: Error, vm: any, info: string) {
  // Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
  // See: https://github.com/vuejs/vuex/issues/1505
  // 在处理错误处理程序时停用deps跟踪,以避免可能的无限渲染。
  pushTarget()
  try {
    if (vm) {
      let cur = vm
      while ((cur = cur.$parent)) {
        // vue实例的errorCaptured生命周期
        const hooks = cur.$options.errorCaptured
        if (hooks) {
          for (let i = 0; i < hooks.length; i++) {
            try {
              const capture = hooks[i].call(cur, err, vm, info) === false
              if (capture) return
            } catch (e) {
              globalHandleError(e, cur, 'errorCaptured hook')
            }
          }
        }
      }
    }
    globalHandleError(err, vm, info)
  } finally {
    popTarget()
  }
}
function globalHandleError (err, vm, info) {
  if (config.errorHandler) {
    try {
      return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      // if the user intentionally throws the original error in the handler,
      // do not log it twice
      if (e !== err) {
        logError(e, null, 'config.errorHandler')
      }
    }
  }
  logError(err, vm, info)
}

function logError (err, vm, info) {
  if (process.env.NODE_ENV !== 'production') {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }
  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console !== 'undefined') {
    console.error(err)
  } else {
    throw err
  }
}

结论:可以看出,错误处理机制会触发vue实例的errorCaptured生命周期(捕获一个来自子孙组件的错误时被调用)

3.思考总结

        从vue实例化的过程中,可以看出其生命周期的调用过程,而不是简单意义上理解的8大生命周期;具体如下:

生命周期描述
beforeCreate组件实例被创建之初
created组件实例已经完全创建
beforeMount组件挂载之前
mounted组件挂载到实例上去之后
beforeUpdate组件数据发生变化,更新之前
updated组件数据更新之后
beforeDestroy组件实例销毁之前
destroyed组件实例销毁之后
activatedkeep-alive 缓存的组件激活时
deactivatedkeep-alive 缓存的组件停用时调用
errorCaptured捕获一个来自子孙组件的错误时被调用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值