Vue源码-初始化流程

414 阅读2分钟

初始化流程

示例代码

代码示例:

new Vue({
    el: '#app',
    data: {
        foo: 'foo'
    }
})

全局挂载Vue - vue.js

  引用vue.js之后,浏览器会自动执行外部引入的script文件。即最开始会初始化Vue的属性与方法。

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.Vue = factory());
}(this, (function () {
    'use strict';
    // vue 代码
});

  初始化流程简化:new Vue() => _init() => $mount() => _render() => _update() Vue实例化流程图.png

Vue构造函数

  文件目录地址: core/instance/index.jsvue.js时, 先执行initMixin/stateMixin/eventsMixin/lifecycleMixin/renderMixin

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'

// 创建构造函数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')
  }
  this._init(options)
}

initMixin(Vue) // 在Vue原型对象上挂载 _init方法
stateMixin(Vue) // 在Vue原型对象上挂载: $data/$props/$set/$del
eventsMixin(Vue) // 在Vue原型对象上挂载: $on/$once/$off/$emit
lifecycleMixin(Vue) // 在Vue原型对象上挂载: _update/$forceUpdate/$destroy
renderMixin(Vue) // 在Vue原型上挂载: $nextTick, _render, 以及各种渲染相关的简写方法挂载

export default Vue

_init

   _init() 方法所在目录地址: core/instance/init.js

 // 核心代码
 Vue.prototype._init = function () {
    // 选项合并:通用默认选项和用户选项合并
    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 {
      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,$root,$children,$refs
    initEvents(vm) // 事件监听
    initRender(vm) // $slots,$createElement
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm) // 状态初始化:data、props、methods/computed
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

合并选项

  将根组件传入的options与当前组件的options进行合并

initLifecycle(vm) 

  初始化组件相关的属性,同时将当前的 组件实例 vm 存入当前组件的父组件的$children属性中  

  在组件实例上挂载:$parents$root, $children ,$refs ,_watcher ,_directives ,_isMounted , _isDestoryted 等属性。

initEvents(vm)

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  // 将父组件上绑定在当前组件的事件 添加到当前组件
  // 子组件自身派发的事件由自身进行监听,执行的事件函数 需要由父组件中获取
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

  vue中自定义事件系统中,子组件派发的事件(即$emit)由子组件自己监听($on)。但实际开发中子组件派发事件的监听函数式保存在父组件中。  

// 示例
// child
<tempalte>
   <div @click="handleClick">子组件</div>
</template>
<script>
export default{
   methods: {
       handleClick() {
           this.$emit('child');
       }
   }
}
</script>

// parent
<child @child="handleChild"></child>

  由上示例中,子组件中有派发(emit)一个事件child,但是监听函数handleChild存在于父组件中。但是由于事件系统中谁派发的事件谁接收,故需要将父组件的事件获取到子组件中,以便于子组件能够自己执行当前事件函数。

  当前initEvents的主要作用: 将子组件中自己派发的事件从父组件中获取,存入当前组件的_events属性中。

initRender(vm)

  给当前组件实例上挂载属性:slotsslots、scopedSlots,_c, $createElement, attrs,attrs, listeners.

callHook(vm, 'beforeCreate')

  执行构造函数beforeCreate

initState(vm)

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

  初始化 props、methods、data,computed、watch。data初始化即 数据响应式 开始的位置

callHook(vm, 'created')

vm.$mount

平台相关 扩展$mount Compile开始阶段

  挂载函数首先调用为当前平台的扩展$mount, 方法所在地址:platforms/web/entry-runtime-with-compiler。 扩展的:Vue.prototype.$mount主要实现功能:

  • tempalte/el/转化为render函数,将render挂载到$options
  • Compile阶段compileToFunctions 生成一个 with包裹的code
  • 调用公共$mount方法

公共 $mount

  公共的$mount方法,所在目录:platforms/web/runtime/index

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

  公共的$mount方法内部非常简洁,获取 宿主元素,返回mountComponent方法的返回结果

mounComponent

  mountComponent方法所在目录:core/instance/lifecycle.jsmountComponent 整个过程就是进行挂载, 将虚拟dom转化为真实dom。

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el

  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._render 将渲染组件,获取vnode
      // vm._update 执行__patch__补丁函数,执行更新,将传入 vnode<虚拟dom> 转换为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
  // 将更新函数当做update函数传入 watcher中,当数据进行修改时,dep.notify 通知更新时 即真正执行的就是当前的updateComponent方法
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        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
}

callHook('vm', 'beforeMount')

updateComponent

  mountComponent方法主要是有vm._rendervm._update以及new Watcher这三个核心功能。每一个组件存在一个对应的组件更新WatcherWatcherupdateComponent当update参数接收,每当Dep通知更新时,Watcher则执行当前更新函数。此处也是 数据响应式 其中数据变化通知组件更新的核心所在。

callHook('vm', 'mounted')

_render

  _render所在目录:core/instance/render.js。主要功能:执行render函数 渲染组件,获取vdom

_update

  _update方法所在目录: core/instance/lifecycle.js_update方法的核心功能: 执行__patch__补丁函数< diff算法核心函数:core/vdom/patch.js >,将传入的 vdom 转化为 真实dom