初始化流程
示例代码
代码示例:
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构造函数
文件目录地址: core/instance/index.js 入vue.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)
给当前组件实例上挂载属性:scopedSlots,_c, $createElement, 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.js。mountComponent 整个过程就是进行挂载, 将虚拟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._render、vm._update以及new Watcher这三个核心功能。每一个组件存在一个对应的组件更新Watcher,Watcher将updateComponent当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