活着,最有意义的事情,就是不遗余力地提升自己的认知,拓展自己的认知边界。
在搭建源码调试环境一节中,我们已经找到了Vue的构造函数,接下来开始探索Vue初始化的流程。
一个小测试
在精读源码之前,我们可以在一些重要的方法内打印一下日志,熟悉一下这些关键节点的执行顺序。(执行npm run dev后,源码变更后会自动生成新的Vue.js,我们的测试html只需要刷新即可)
在初始化之前,Vue类的构建过程?
在此过程中,大部分都是原型方法和属性,意味着实例vm可以直接调用
注意事项:
1、以$为前缀的属性和方法,在调用_init原型方法的那一刻即可使用
2、以_为前缀的原型方法和属性,谨慎使用
3、本章旨在了解Vue为我们提供了哪些工具(用到时,深入研究,不必要在开始时花过多精力,后边遇到时会详细说明)
4、类方法和属性在new Vue()前后都可以使用,原型方法和属性只能在new Vue()后使用
定义构造函数
// src/core/instance/index.jsfunction Vue (options) { //形式上很简单,就是一个_init方法 this._init(options)}
挂载原型方法:_init
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }
挂载与state相关的原型属性和原型方法
// src/core/instance/state.js const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { //略 }
挂载与事件相关的原型方法
// src/core/instance/events.js const hookRE = /^hook:/ Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {} Vue.prototype.$once = function (event: string, fn: Function): Component {} Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {} Vue.prototype.$emit = function (event: string): Component {}
挂载与生命周期相关的原型方法
// src/core/instance/lifecycle.js Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {} Vue.prototype.$forceUpdate = function () {} Vue.prototype.$destroy = function () {}
挂载与渲染相关的原型方法
// install runtime convenience helpers installRenderHelpers(Vue.prototype) Vue.prototype.$nextTick = function (fn: Function) {} Vue.prototype._render = function (): VNode {}
挂载Vue类方法和类属性
// src/core/global-api/index.js// config const configDef = {} configDef.get = () => config Object.defineProperty(Vue, 'config', configDef) Vue.util = { warn, extend, mergeOptions, defineReactive } 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 = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) Vue.options._base = Vue extend(Vue.options.components, builtInComponents) initUse(Vue) //挂载类方法use,用于安装插件(特别特别重要) initMixin(Vue) //挂载类方法mixin,用于全局混入(在Vue3中被新特性取代) initExtend(Vue) //实现Vue.extend函数 initAssetRegisters(Vue)//实现Vue.component, Vue.directive, Vue.filter函数
挂载平台相关的属性,挂载原型方法$mount
// src/platforms/web/runtime/index.js// install platform specific utilsVue.config.mustUseProp = mustUsePropVue.config.isReservedTag = isReservedTagVue.config.isReservedAttr = isReservedAttrVue.config.getTagNamespace = getTagNamespaceVue.config.isUnknownElement = isUnknownElement// install platform runtime directives & componentsextend(Vue.options.directives, platformDirectives)extend(Vue.options.components, platformComponents)// install platform patch functionVue.prototype.__patch__ = inBrowser ? patch : noopconsole.log('挂载$mount方法')// public mount methodVue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component {}
拓展$mount方法
// src/platforms/web/entry-runtime-with-compiler.jsconst mount = Vue.prototype.$mount //保存之前定义的$mount方法Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean): Component { //执行拓展内容 return mount.call(this, el, hydrating) //执行最初定义的$mount方法}
Vue的初始化过程(很重要哦!!!)
熟悉了初始化过程,就会对不同阶段挂载的实例属性了然于胸,了解Vue是如何处理options中的数据,将初始化流程抽象成一个模型,从此,当你看到用户编写的options选项,都可以在这个模型中演练。
前边我们提到过,Vue的构造函数中只调用了一个_init方法
执行_init方法
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { const vm: Component = this //此刻,Vue的实例已经创建,只是雏形,但Vue的所有原型方法可以调用 // a flag to avoid this being observed //(observe会在后面的响应式章节详细说明) vm._isVue = true // merge options if (options && options._isComponent) {// 在后面的Vue组件章节会详细说明 // 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(//合并options resolveConstructorOptions(vm.constructor),//主要处理包含继承关系的实例() options || {}, vm ) } // expose real self vm._self = vm initLifecycle(vm) //初始化实例中与生命周期相关的属性 initEvents(vm) //处理父组件传递的事件和回调 initRender(vm) //初始化与渲染相关的实例属性 callHook(vm, 'beforeCreate') //调用beforeCreate钩子,即执行beforeCreate中的代码(用户编写) initInjections(vm) // resolve injections before data/props 获取注入数据 initState(vm) //初始化props、methods、data、computed、watch initProvide(vm) // resolve provide after data/props 提供数据注入 callHook(vm, 'created') //执行钩子created中的代码(用户编写) if (vm.$options.el) {//DOM容器(通常是指定id的div) vm.$mount(vm.$options.el) //将虚拟DOM转换成真实DOM,然后插入到DOM容器内 } }
initLifecycle:初始化与生命周期相关的实例属性
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }0
initEvents(vm):处理父组件传递的事件和回调
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }1
initRender(vm):初始化与渲染相关的实例属性
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }2
CallHook(vm, 'beforeCreate'):执行beforeCreate钩子
执行options中,用户编写在beforeCreate中的代码
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }3
initInjections(vm): resolve injections before data/props 获取注入数据
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }4
initState(vm):初始化props、methods、data、computed、watch(划重点啦!!!)
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }5
initProps: 初始化props
此处概念比较多,propsData、 props、vm._props、 propsOptions,后续会结合实例来分析其区别,此处只做大概了解。
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }6
initMethods:初始化methods
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }7
initData: 初始化data
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }8
initComputed:初始化computed 选项
// src/core/instance/init.jsVue.prototype._init = function (options?: Object) { }9
initWatch:初始化watch
createWatcher:本质上执行了vm.$watch(expOrFn, handler, options)
// src/core/instance/state.js const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { //略 }0
initProvide(vm): 提供数据注入
为什么provide初始化滞后与inject,后续补充
// src/core/instance/state.js const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { //略 }1
CallHook(vm, 'created'): 执行created钩子中的代码
callHook的相关逻辑,参考上面的callHook(vm, 'beforeCreate')
执行挂载
执行$mount扩展
通过下面的代码可知:当用户代码中同时包含render,template,el时,它们的优先级依次为:render、template、el
// src/core/instance/state.js const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { //略 }2
$mount方法中,首先获取挂载容器,然后执行mountComponent方法
// src/core/instance/state.js const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { //略 }3
// src/core/instance/state.js const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { //略 }4
在_update方法中,通过_vnode属性判断是否初次渲染,patch 其实就是patch方法,关于patch的详细逻辑,将在diff算法章节详细说明。
// src/core/instance/state.js const dataDef = {} dataDef.get = function () { return this._data } const propsDef = {} propsDef.get = function () { return this._props } Object.defineProperty(Vue.prototype, '$data', dataDef) Object.defineProperty(Vue.prototype, '$props', propsDef) Vue.prototype.$set = set Vue.prototype.$delete = del Vue.prototype.$watch = function ( expOrFn: string | Function, cb: any, options?: Object ): Function { //略 }5原文:https://juejin.cn/post/7100124377567461383