距离本小白接触前端开发也已经快将近一年了,从开始的连闭包的原理都须要了解一个晚上(虽然目前深刻其原理我也说不明白)到如今可以本身独立完成一个简单的单页webapp,回顾近一年的时间,也是按照前人留下的足迹,一步一个脚印的走,从html到css再到js再到jQuery库,再到webpack,直到Vue。一路走来较为顺利,单也有磕绊,也有疑惑。开始接触Vue的时候,甚为惊艳!居然有如此之写法,简洁,方便。可是使用过程固然难免充满了疑惑,为何是这样的?为何能这样写。在通过近一年的打磨以后,本小白也决定开始研究、研读Vue源码,但愿在Vue3.0来临之际,将2.6.X源码研究一遍,为日后3.0打基础,而且将这一年来心中的很多疑惑悉数解开!也但愿本文对一样在Vue道路上的朋友有所帮助~javascript
磨刀不误砍柴工,再开始研读以前,咱们须要了解一下Vue的文件的架构以及Vue源码编写使用到的静态类型检查器FLOW。css
咱们从github上面下载最新的vue的源码后,能够看到vue的源码项目文件仍是很是庞大的,和通常的webpack相似,咱们Vue源码也存在src文件当中。 html
core文件就是咱们这一篇要讲得的核心也是Vue实例初始化的核心所在。前端
platform文件夹存放的是咱们的vue如何进入到运行时的,区分是经过cli打包的文件仍是直接在html中引用的状况等。vue
server文件中存放生成render模板编译的相关代码,例如咱们的template是如何编译成render函数,逻辑在这个文件夹中。java
FLOW是一个静态类型检查器,因为Vue中存在大量的类,引入flow静态检查器来确保类属性不出错,用法相似于TypeScript,这里就很少展开介绍了(实际上是不会)。node
正文开始了!首先,咱们找到Vue类定义的位置src/core/instance/index.js
。webpack
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 复制代码
在这里看到Vue类的定义很是"简单"= =;作了一个判断防止调用Vue函数,而后就是一个初始化this._init(options)
。而_init 就是在initMixin(Vue)
是混入到类中的。options就是咱们常常main.js写的那个传入的对象:git
new Vue({ render: h => h(App), }).$mount('#app') 复制代码
那么,接下来 咱们就要看一下initMixin到底对Vue作了什么,给这个类添加了什么功能,以及_init()函数到底作了哪些事情。github
从上面代码能够看到initMixin
在同级目录下init.js文件中,咱们来看一下initMixin
到底干了什么。
export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { //some codes } } export function initInternalComponent (vm: Component, options: InternalComponentOptions) { //some codes } export function resolveConstructorOptions (Ctor: Class<Component>) { //some codes } function resolveModifiedOptions (Ctor: Class<Component>): ?Object { //some codes } 复制代码
好吧整个文件去掉逻辑部分仍是很清晰的,暴露四个方法,能够看到initMixin
就是只为Vue添加了一个_init
方法,那么接下来咱们就重点来看一下_init方法,在Vue实例化的过程当中,作了什么吧~
首先,先把源码贴上~
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) //实例的首次挂载 } } 复制代码
咱们一点点来说,首先判断咱们传入的options,根据是否存在options._isComponent
来判断如何处理options。Vue的初始化逻辑固然是走下面的,经过mergeOptions()
合并了父类(这里是指Vue)options和当前options。那么好奇的童鞋确定要问了,Vue哪来的options??!!这里卖个关子,后面会讲到的~仍是比较重要的一块,因此留个悬念,也算是加深印象~
能够看到接下来就是一系列的init初始化,咱们本章大体介绍一下每一个init都是干啥的,具体展开讨论会放到后面专门的文中介绍~首先是initLifecycle
:它主要是针对生命周期作一些初始化;initEvents
:对vue的监听事件的初始化;initRender
:初始化Vue的render方法,本文会花必定篇幅展开;callhook:beforeCreate
:调起预先设定的beforeCreate钩子函数(没错生命周期钩子函数就在这里慢慢开始了);initInjections(vm)
:后代组件injection相关初始化;initState(vm)
:初始化data、props、methods等属性,数据双向绑定的核心所在,以后文章会详细展开。initProvide(vm)
:后代组件initProvide相关初始化;callHook(vm, 'created')
:调起预先设定的created钩子函数。
vm.$mount(vm.$options.el)
最后一步就是调用vm.$mount()
方法进行实例的挂载。咱们能够看到,若是咱们传入的属性若是有el属性,才会调用,否则是不会调用的。因此咱们在new Vue是会有两种状况:
new Vue({ render: h => h(App), }).$mount('#app')//不走_init()的mount本身调用 new Vue({ el:'#app'//_init直接调用mount render: h => h(App), }) 复制代码
到这里,_init(options)
的流程也就结束了,虽而后面的文章咱们还会无数次提到他。。。接下来咱们就来一块儿看一下vm.$mount()
到底干了些什么,以及具体他是如何挂载咱们的Vue实例的。
vm.$mount(el)
定义在src/platforms/web/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
里面。咱们看一下代码:
export function mountComponent ( //vue实例挂载的方法 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) //挂载实例,调用 } } 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 } 复制代码
咱们一步步看他的逻辑来看他的逻辑。一、!vm.$options.render
判断若是实例不存在render函数,则建立一个空的vnode
。二、callHook(vm, 'beforeMount')
原来这个钩子函数在这儿~三、调用了_update()里面两个参数,vm._render()建立了一个node节点,接下来咱们会重点展开。
let updateComponent () => { vm._update(vm._render(), hydrating) } 复制代码
四、new Watcher()添加了一个观察者,这是一个render Watcher监听数据变化后展开节点渲染。(后面介绍watcher的时候会重点讲)目前咱们只要知道watcher在初始化会调用传入的updateComponent函数,从而触发渲染和实例挂载。五、callHook(vm, 'mounted')
:mounted钩子函数都不陌生。到这里vm.$mount函数也就走完了!
原本打算把vm._update(vm._render(), hydrating)
也放在这里面讲的,可是一想,若是仓促展开,可能有些细节回顾及不到并且vm._update()
和vm._render()
也是比较重要的两块一块涉及patch(确定就会涉及到diff算法)另外一块涉及到Vnode建立,如今本小白也处于比较没有思绪的状况,不知如何展开你们会比较容易接受,须要再整理整理大纲在接着往下来写~因此就先到这儿吧!在这里也贴一下目前本小白学习整理的大纲:Vue源码解读
有大佬们须要短信业务,或者号码认证的能力的话,能够看看这里!中国联通创新能力平台 运营商官方平台!没有中间商赚差价~