Skip to content

vue源码阅读之初始化过程 #3

@tanxchen

Description

@tanxchen

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-show` 和 `v-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')

Metadata

Metadata

Assignees

No one assigned

    Labels

    Vueabout Vue.js

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions