import阶段

从打包入口文件入手,scripts/config.js, ➡️ resolve('web/entry-runtime.js') ⤵️ 入口文件 src/platforms/entry-runtime.js, platforms 文件夹是跟平台相关的代码 ⤵️ src/platforms/web/runtime/index.jsruntime/index.js 里对该运行时代码做了特殊处理 ⤵️ src/code/index.js,真正入口文件

import Vue from './instance/index'
// ...
initGlobalAPI(Vue)
// ...ssr相关代码
Vue.version = '__VERSION__'

export default Vue

⤵️ src/core/instance/index.js

function Vue (options) {
  // ...
  this._init(options)
}
// 添加 _init 函数
initMixin(Vue)
// 主要是添加了 $data,$props,$watch,$set,$delete 几个属性和方法
stateMixin(Vue)
// 主要是添加了 $on,$off,$once,$emit 三个方法
eventsMixin(Vue)
// 主要添加了 _update, $forceUpdate, $destroy 三个方法
lifecycleMixin(Vue)
// 主要添加了 $nextTick 和 _render 两个方法以及一大堆renderHelpers
renderMixin(Vue)

export default Vue

instance/index.js 作用是按功能模块往 Vue 原型和自身添加了许多属性和方法。 再回到 initGlobalAPI(Vue).

// 一般使用的是实例里的方法
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick

Vue.options = Object.create(null) // 循环出来的结果其实是三个 components,directives, filters ASSET_TYPES.forEach(type => { Vue.options[type + ‘s’] = Object.create(null) })

// this is used to identify the “base” constructor to extend all plain-object // components with in Weex’s multi-instance scenarios. Vue.options._base = Vue

// builtInComponents 仅为内置组件 KeepAlive extend(Vue.options.components, builtInComponents)

initUse(Vue) // 添加 Vue.use initMixin(Vue) // 添加 Vue.mixin initExtend(Vue) // 添加 Vue.extend // 添加 Vue.component, Vue.directive, Vue.filter 方法 initAssetRegisters(Vue)

initGlobalAPI 作用也是继续添加了一些全局方法。 现在 function Vue 大致就变成了如下样子:

//构造函数
function Vue () {
  this._init()
}

//全局config对象,我们几乎不会用到 Vue.config = { keyCodes, _lifecycleHooks: [‘beforeCreate’, ‘created’, ] }

// 默认的options配置,我们每个组件都会继承这个配置。 Vue.options = { beforeCreate, // 比如 vue-router 就会注册这个回调,因此会每一个组件继承 components, // 前面提到了,默认组件有三个 KeepAlive,transition, transitionGroup,这里注册的组件就是全局组件,因为任何一个组件中不用声明就能用了。所以全局组件的原理就是这么简单 directives, // 默认只有 v-showv-model filters }

//一些全局方法 Vue.use // 注册插件 Vue.component // 注册组件 Vue.directive // 注册指令 Vue.nextTick //下一个tick执行函数 Vue.set/delete // 数据的修改操作 Vue.mixin // 混入mixin用的

//Vue.prototype 上有几种不同作用的方法

//由initMixin 添加的 _init 方法,是Vue实例初始化的入口方法,会调用其他的功能初始话函数 Vue.prototype._init

// 由 initState 添加的三个用来进行数据操作的方法 Vue.prototype.$data Vue.prototype.$props Vue.prototype.$watch Vue.prototype.$set Vue.prototype.$delete

// 由initEvents添加的事件方法 Vue.prototype.$on Vue.prototype.$off Vue.prototype.$one Vue.prototype.$emit

// 由 lifecycle添加的生命周期相关的方法 Vue.prototype._update Vue.prototype.$forceUpdate Vue.prototype.$destroy

//在 platform 中添加的生命周期方法 Vue.prototype.$mount

// 由renderMixin添加的$nextTick_render 以及一堆renderHelper Vue.prototype.$nextTick Vue.prototype._render Vue.prototype._b Vue.prototype._e //…

实例化阶段[new Vue({…})]

代码在 src/core/instance/init.js 里。 主要功能代码:

  • 生成自增的唯一ID标识 vm._uid = uid++
  • 合并 vm.constructor 和传入的 options,生成 vm.$options
    • 这一步使得可以在子组件能够使用全局的 directives、filters 等方法
  • 挂载自身 vm._self = vm
  • 初始化生命周期、事件、渲染等相关钩子工作
    • initLifecycle(vm)
      • 定位第一个非抽象父级,并添加到 $children 里,parent.$children.push(vm)
      • 添加很多变量,主要为 $parent、$children,$refs 也在这里定义了,其他 _开头的变量均为生命周期不同阶段状态的 flag
        • vm.$parent = parent
        • vm.$root = parent ? parent.$root : vm
        • vm.$children = []
        • vm.$refs = {}
        • vm._watcher = null
        • vm._inactive = null
        • vm._directInactive = false
        • vm._isMounted = false
        • vm._isDestroyed = false
        • vm._isBeingDestroyed = false
    • initEvents(vm)
      • 注册的是父组件事件

    • initRender(vm)
      • 做 render 的准备工作,并未开始 render,如创建 vm._vnode,vm.$createElement,$attrs 和 $listeners
    • callHook(vm, ‘beforeCreate’)
      • 调用 beforeCreate 生命周期钩子
      • callHook 里定义了:if (vm._hasHookEvent) {vm.$emit('hook:' + hook)},所以有个小技巧实现在在父组件通过 hook 钩子,监听子组件生命周期方法:

      •   <Chind-Component @hook:updated="doSomething" /> 
        
    • initInjections(vm) // resolve injections before data/props
    • initState(vm)
      • data, props, computed 等都是在这里初始化的,常见的面试考点比如Vue是如何实现数据响应化的 答案就在这个函数中寻找
    • initProvide(vm) // resolve provide after data/props
    • callHook(vm, ‘created’)
      • 调用 created 生命周期钩子
  • 存在 el ,则调用 $mountvm.$mount(vm.$options.el)
    • 当然,el 也可以不写,而是在实例化的时候直接调用: new Vue({...}).$mount('#app')