|

Aimee

Write the Code. Change the World.

vue2 源码解析-初始化

· 分享镜#vue

源码目录结构

src
  ├─compiler   编译相关
  ├─core       Vue核心库
  ├─platforms  平台相关代码
  ├─server     SSR,服务端渲染
  ├─sfc        .vue 文件编译为 js 对象
  └─shared     公共的代码

Vue 的不同构建版本

  • 完整版:同时包含编辑器和运行时的版本
  • 编辑器:用来将模板字符串编译称为JavaScript渲染函数的代码,体积大、效率低。
  • 运行时:用来创建Vue实例、渲染并处理虚拟DOM等的代码,体积小、效率高。基本上就是除去编译器的代码。比完整版体积少30%左右。
    • *.vue文件中的模板是在构建时预编译的,最终打包后的结果不需要编译器,只需要运行时版本即可
  • UMD
  • CommonJs
  • ES Module

从入口开始

  • src/platform/web/entry-runtime-with-compiler.js

Vue 的构造函数在哪里

  • src/platform/web/entry-runtime-with-compiler.js 中引用了 './runtime/index'
  • src/platform/web/runtime/index.js
    • 设置 Vue.config
    • 设置平台相关的指令和组件
      • 指令 v-model、v-show
      • 组件 transition、transition-group
    • 设置平台相关的 patch 方法(打补丁方法,对比新旧的 VNode)
    • 设置 $mount 方法,挂载 DOM
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
  • src/platform/web/runtime/index.js 中引用了 'core/index'
  • src/core/index.js
    • 定义了 Vue 的静态方法
    • initGlobalAPI(Vue)
      • extend
      • mixin
      • use
  • src/core/index.js 中引用了 './instance/index'
  • src/core/instance/index.js
    • 定义了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')
	}
	// 调用 _init() 方法
  this._init(options)
}
// 注册 vm 的 _init() 方法,初始化 vm 
initMixin(Vue)
// 注册 vm 的 $data/$props/$set/$delete/$watch 
stateMixin(Vue)
// 初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy 
lifecycleMixin(Vue)
// 混入 render
// $nextTick/_render
renderMixin(Vue)

四个导出 Vue 的模块

  • src/platforms/web/entry-runtime-with-compiler.js
    • web 平台相关的入口
    • 重写了平台相关的 $mount() 方法
    • 注册了 Vue.compile() 方法,传递一个 HTML 字符串返回 render 函数
  • src/platforms/web/runtime/index.js
    • web 平台相关
    • 注册和平台相关的全局指令:v-model、v-show
    • 注册和平台相关的全局组件: v-transition、v-transition-group
    • 全局方法:
      • patch:把虚拟 DOM 转换成真实 DOM
      • $mount:挂载方法
  • src/core/index.js
    • 与平台无关
    • 设置了 Vue 的静态方法,initGlobalAPI(Vue)
  • src/core/instance/index.js
    • 与平台无关
    • 定义了构造函数,调用了 this._init(options) 方法
    • 给 Vue 中混入了常用的实例成员

Vue 的初始化

静态方法初始化

// 注册 Vue 的静态属性/方法 
initGlobalAPI(Vue)
// src/core/global-api/index.js
// 初始化 Vue.config 对象 Object.defineProperty(Vue, 'config', configDef)
// exposed util methods.
// NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk.
// 这些工具方法不视作全局API的一部分,除非你已经意识到某些风险,否则不要去依赖他们 
Vue.util = {
  warn,
  extend,
  mergeOptions,
  defineReactive
}
// 静态方法 set/delete/nextTick 
Vue.set = set
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
// 让一个对象可响应
Vue.observable = <T>(obj: T): T => {
  observe(obj)
return obj }
// 初始化 Vue.options 对象,并给其扩展
// components/directives/filters/_base 
Vue.options = Object.create(null) 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
// 设置 keep-alive 组件 
extend(Vue.options.components, builtInComponents)
// 注册 Vue.use() 用来注册插件 
initUse(Vue)
// 注册 Vue.mixin() 实现混入 
initMixin(Vue)
// 注册 Vue.extend() 基于传入的 options 返回一个组件的构造函数 
initExtend(Vue)
// 注册 Vue.directive()、 Vue.component()、Vue.filter() 
initAssetRegisters(Vue)

Vue实例初始化

  • 定义 Vue 的构造函数
  • 初始化 Vue 的实例成员
// 此处不用 class 的原因是因为方便,后续给 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)
}
// 注册 vm 的 _init() 方法,初始化 vm 
 initMixin(Vue)
// 注册 vm 的 $data/$props/$set/$delete/$watch 
 stateMixin(Vue)
// 初始化事件相关方法
// $on/$once/$off/$emit
eventsMixin(Vue)
// 初始化生命周期相关的混入方法
// _update/$forceUpdate/$destroy 
lifecycleMixin(Vue)
// 混入 render
// $nextTick/_render
renderMixin(Vue)
  • initMixin(Vue)
    • 初始化_init()方法
 // 给 Vue 实例增加 _init() 方法
  // 合并 options / 初始化操作  
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
    // 如果是 Vue 实例不需要被 observe
    vm._isVue = true
    // merge options
    // 合并 options
    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
    // vm 的生命周期相关变量初始化
    // $children/$parent/$root/$refs
    initLifecycle(vm)
    // vm 的事件监听初始化, 父组件绑定在当前组件上的事件
    initEvents(vm)
    // vm 的编译render初始化
    // $slots/$scopedSlots/_c/$createElement/$attrs/$listeners
    initRender(vm)
    // beforeCreate 生命钩子的回调
    callHook(vm, 'beforeCreate')
    // 把 inject 的成员注入到 vm 上
    initInjections(vm) // resolve injections before data/props
    // 初始化 vm 的 _props/methods/_data/computed/watch
    initState(vm)
    // 初始化 provide
    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)
    }
    // 调用 $mount() 挂载
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }

首次渲染过程

  • Vue 初始化,实例成员,静态成员
  • new Vue()
  • this._init()
    • 合并options
    • initLifecycle(vm) 初始化生命周期的相关变量
    • initEvents(vm) 将父组件的事件绑定到当前实例的事件中
    • initRender(vm) 初始化 slots /createElement/ attrs/listeners
    • 执行beforeCreate 钩子
    • initInjections(vm) 把 inject 的成员注入到 vm 上
    • initState(vm) 初始化状态相关的参数,data/props/methods/computed/watch
    • initProvide(vm) 初始化provide
    • 执行created 钩子
    • 如果传递了el 直接执行 $mount()
  • vm.$mount()
    • 编辑版本
      • 重写$mount
      • 如果传递了render 直接执行
      • 如果没有传递render 获取template,生成render 渲染函数
    • 运行时版本
      • 调用mountComponent()
  • mountComponent(this, el)
    • 触发beforeMount钩子
    • 定义updateComponent
      • vm._update(vm._render)
      • vm._render()渲染,虚拟dom
      • vm.update()更新,将VDom 转换成真实dom
    • 创建Watcher实例
      • before 中执行beforeUpdate钩子
    • 触发mounted
    • return vm
  • watcher.get()
    • 创建完watcher 会调用一次get
    • 调用updateComponent()
    • 调用vm._render() 创建VNode
    • 调用vm.update()
      • 调用vm.patch(vm.$el, vnode) 挂载真实dom
      • 记录vm.$el

简述

  • 初始化Vue 静态方法和实例方法
  • 实例化Vue new Vue()
  • 执行this._init() 处理vm 的参数
  • 执行beforeCreate 钩子
  • 创建vm的data、props、methods等
  • 执行created钩子
  • 执行$mount()
    • 编辑版本
      • 将template处理生成render 函数
    • 运行时版本
      • 执行mountComponent()
  • mountComponent()
    • 执行beforeMount 钩子
    • 生成updateComponent 函数
      • vm._update(vm._render)
    • 添加Watcher 实例
    • Watcher中before 参数中执行beforeUpdate钩子
    • Watcher实例化会执行一次get方法 将执行updateComponent函数
  • updateComponent函数中
    • vm._render() 生成虚拟dom并返回vnode
    • vm._update()中有个vm.patch(vm.$el, vnode)将vnode挂载到真实dom中
    • 记录vm.$el 作为下一次对比的旧vnode

评论6

登录后参与评论。

  • 游客

    微笑

  • 游客

    厉害

  • 游客

    666

  • 游客

    嘻嘻

  • 游客

    666

  • 游客

    666

回到顶部