共计 9108 个字符,预计需要花费 23 分钟才能阅读完成。
咱们应用 vue-cli 搭建 vue 2.x 我的项目时,大抵由如下代码来做一个 vue 利用的初始化:
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({render: (h) => h(App),
}).$mount("#app");
咱们能够就从此处开始对 Vue 的意识。能够看到,这里外表上只做了一个简略的工作,就是通过 new 操作创立了一个 vue 的实例,并传递了一个配置项对象,该对象蕴含了一个 render 办法。
依据这个调用,咱们找到 src/core/instance/index.js
文件,内容如下:
// src/core/instance/index.js
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'
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)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
内容也很直观,这里定义了一个只承受 new 结构调用的 Vue Function,并对 Vue 进行了一系列的混入操作。
再浅显地看一下这些 Mixin 都做了什么,能够看到是往 Vue 的 prototype 对象上挂了一些属性和办法。
大抵如下:
Vue.prototype
|- initMixin
|- _init(options?: Object)
|- stateMixin
|- $data
|- $props
|- $set(target: Array<any> | Object, key: any, val: any): any <- ../observer/index
|- $delete(target: Array<any> | Object, key: any) <- ../observer/index
|- $watch(expOrFn: string | Function, cb: any, options?: Object): Function
|- eventMixin
|- $on(event: string | Array<string>, fn: Function): Component
|- $once(event: string, fn: Function): Component
|- $off(event?: string | Array<string>, fn?: Function): Component
|- $emit(event: string): Component
|- lifecycleMixin
|- $_update(vnode: VNode, hydrating?: boolean)
|- $forceUpdate()
|- $destrouy()
|- renderMixin
|- $nextTick(fn: Function)
|- _render(): VNode
Vue 的函数体中,调用了一个 _init
的办法,并将参数传入,能够看到,_init
办法是在 initMixin 中定义的。
持续看 _init
办法的定义:
// src/core/instance/init.js
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
vm._isVue = true
// merge 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
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
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)
}
if (vm.$options.el) {vm.$mount(vm.$options.el)
}
}
见名知意,这个函数是对 vue 实例做一系列的初始化操作。
- 获取 vue 实例的结构器以及父级结构器(顺次递归)上的配置项,以及参数传递进来的配置项,在加上实例自带的属性,都合并到一起,挂在实例的 $option 属性身上
- 将 vue 实例本身挂在_renderProxy 属性上
-
初始化数据和办法前做一些筹备工作
- initLifecycle:初始化生命周期
- initEvents:初始化事件
- initRender:初始化 render
- 触发
beforeCreate
钩子
-
初始化数据和办法
- initInjections:解决 $options.inject,对注入的数据做响应式解决
-
initState 做的几件事
- initProps:对 $options.props 做响应式解决
- initMethods:对 $options.methods 对象做解决,将所有的办法间接挂在实例对象上,并将办法的 this 绑定到 vue 实例对象
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
-
initData:对 $options.data 进行 observe
observe(data, true /* asRootData */)
,持续追踪能够看到observe
办法是对 data 进行响应式解决,返回一个Observer
实例// src/core/boserver/index.js export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() this.vmCount = 0 def(value, '__ob__', this) if (Array.isArray(value)) {if (hasProto) {protoAugment(value, arrayMethods) } else {copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else {this.walk(value) } } /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */ walk (obj: Object) {const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i]) } } /** * Observe a list of Array items. */ observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i]) } } }
-
initComputed:解决计算属性 $options.computed
给每个计算属性创立 Watcher 实例
// src/core/instance/state.js const computedWatcherOptions = {lazy: true} function initComputed(vm: Component, computed: Object) { // ... const watchers = (vm._computedWatchers = Object.create(null)) // ... const isSSR = isServerRendering() for (const key in computed) {const userDef = computed[key] const getter = isFunction(userDef) ? userDef : userDef.get if (__DEV__ && getter == null) {warn(`Getter is missing for computed property "${key}".`, vm) } if (!isSSR) { // create internal watcher for the computed property. watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) } if (!(key in vm)) {defineComputed(vm, key, userDef) } // ... } // ... } export function defineComputed ( target: any, key: string, userDef: Object | Function ) {const shouldCache = !isServerRendering() if (typeof userDef === 'function') { sharedPropertyDefinition.get = shouldCache ? createComputedGetter(key) : createGetterInvoker(userDef) sharedPropertyDefinition.set = noop } else {// ...} // ... Object.defineProperty(target, key, sharedPropertyDefinition) } function createComputedGetter (key) {return function computedGetter () {const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) {if (watcher.dirty) {watcher.evaluate() } if (Dep.target) {watcher.depend() } return watcher.value } } }
能够看到创立 Watcher 实例时传入一个配置项
{lazy: true}
,再看Watcher
的结构器中的代码,即默认watcher.dirty
为true
,所以执行watcher.evaluate()
,watcher.get()
。watcher.get()
会去执行计算方法或者计算属性的get()
办法,即this.getter.call(vm, vm)
。// src/core/observer/watcher.js constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) { this.vm = vm if (isRenderWatcher) {vm._watcher = this} vm._watchers.push(this) // options if (options) { // ... this.lazy = !!options.lazy // ... } else {// ...} // ... this.dirty = this.lazy // for lazy watchers // ... } evaluate () {this.value = this.get() this.dirty = false } get() {pushTarget(this) let value const vm = this.vm try {value = this.getter.call(vm, vm) } catch (e: any) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`) } else {throw e} } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) {traverse(value) } popTarget() this.cleanupDeps()} return value } depend() { let i = this.deps.length while (i--) {this.deps[i].depend()} }
-
initWatch:解决自定义监听 $options.watch
执行了
$watch
办法,能够先看下它的定义:// src/core/instance/state.js Vue.prototype.$watch = function (expOrFn: string | (() => any), cb: any, options?: Record<string, any> ): Function { const vm: Component = this if (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options) } options = options || {} options.user = true const watcher = new Watcher(vm, expOrFn, cb, options) if (options.immediate) {const info = `callback for immediate watcher "${watcher.expression}"` pushTarget() invokeWithErrorHandling(cb, vm, [watcher.value], vm, info) popTarget()} return function unwatchFn() {watcher.teardown() } }
能够看到也是创立了一个
Watcher
实例对象。
- initProvide:解决 $options.provide,将 provide 的数据(或者 provide 执行后的数据)挂在实例的
_provided
属性上 - 触发
created
钩子
-
最初执行
vm.$mount
办法,执行挂载流程,因为挂载的形式由平台决定,所以$mount
的办法并未定义在src/core
中;web 端的$mount
办法定义在src/platforms/web/runtime/index.js
中。// src/platforms/web/runtime/index.js Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component {el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
调用的
mountComponent(this, el, hydrating)
定义在src/core/instance/lifecycle.js
中。// src/core/instance/lifecycle.js export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template' + 'compiler is not available. Either pre-compile the templates into' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } 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._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 new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {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 }
见名知意,是对挂载的解决:
- 拿到
el
放在 vm.$el 上 - 确认是否有
vm.$options.render
,没有则赋值创立一个空的 VNode 实例的办法 - 触发
beforeMount
钩子 -
创立一个新的
Watcher
实例,用于实例更新后触发从新渲染updateComponent = () => {vm._update(vm._render(), hydrating) }
并传递一个 before 办法,用于在组件更新前触发
beforeUpdate
钩子 - 触发
mounted
钩子
- 拿到
Vue 利用初始化大抵就是这样一个流程