Vue源码学习1-new Vue的流程
前言
随着互联网行业日渐增大的竞争压力,前端的难度也在不停的提升,只会使用框架已经不能帮我们找到更好的工作了,深入学习框架中的基础知识才是立身之本。
2020年了,Vue3的正式版前不久也出来了,咱还有必要看Vue2.x的源码吗?先说说Vue3吧,先不管它的性能提升多少,API改动有多大,在它的全家桶套餐完成之前,我觉得是不会有多少人投入实际生产的。全家桶完善之后,估计会有很多新项目使用它,但是老项目就别搞迁移了,麻烦死了。这一整个流程走下来,Vue3慢慢的成熟,怎么着也得明年去了,现在Vue2.x的使用人群这么广,Vue3想要完全取代得花不少时间。所以不要纠结该不该看2.x,干就完事儿了,2.x自己都能轻松搞定了,有时间了再去搞定3也不迟嘛。
学习框架源码之前,一定要熟练运用框架,如果不熟练就来学习源码,那是在找虐。
看源码如果非必要,且勿死磕细节!
--以下的vm都代指vue实例
--以下所有代码来自Vue 2.6.11 版本
初始化
使用Vue的第一步,都是new Vue(options) ,创建一个 vm
在Vue的源代码目录 src/core/instance/index.js 中,可以看到这些代码,Vue就是一个普通的函数,下面的一系列 mixin 操作则是通过prototype为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) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
_init
_init是Vue方法体唯一执行的方法,它是在 initMixin 中绑定的,进入_init方法中查看,以下是其中的关键步骤,通过方法名(见名知意)可以清晰的看到整个的流程,
可以看到熟悉的两个Hook方法 beforeCreate和 created ,其中 initState 方法就是处理 props methods data computed watch 这些和响应式相关的东西
所以 beforeCreate 中无法访问到 data 的数据而 created 中能访问data 的数据就是这么个原因
//-------------------------- 代码省略 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') //--------------------- 代码省略 if (vm.$options.el) { vm.$mount(vm.$options.el) }
$mount
前面的一些初始化的工作做好了之后,就轮到$mount方法了,它被叫做挂载,作用就是将 vm 和 vm.$options.el(new Vue 时传入的dom节点)进行绑定,以达到数据绑定的效果,绑定完成后就可以单纯的通过数据驱动视图的变化(Vue默认的是单向的数据绑定,也就通过data驱动view,view的变化其实不会影响data,但是可以通过 v-model 等语法手动达成双向数据绑定)
$mount方法也可以手动调用 new Vue(options).$mount('el') options中就不能传入 el 参数了,最后的效果都是一样的
我们可以在 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) }
但同时,我们又可以在 src/platforms/web/entry-runtime-with-compiler.js 中找到另一个定义,这里的操作会把上面绑定的$mount方法缓存起来,然后再做一些其他的操作,最后再次调用原有的mount方法,这里的其他操作其实就是 模板编译 ,通过上面那个文件名称就可以看的出来 runtime-whit-complier
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) ................... ................代码省略 模板编译流程 return mount.call(this, el, hydrating) }
至于为什么这做是有原因的,以前最原始的使用方式(Vue1.x)的时候,都是直接在html上使用vue的语法
2.x的开始使用ES6进行了重写,也使用了打包工具,后面还提供了Vue-Cli帮助开发者更便捷的构建和开发项目,因为使用了webpack,Vue使用Loader提供了一种更好的书写Vue代码的方方式,就是 xxxx.vue 单文件组件,vue-loader 可以将书写好的.vue模板文件处理编译成 render 函数,省去了在浏览器编译模板的过程,极大的增加了Vue在浏览器中的在运行速度
Vue在构建代码的时候,提供了多种构建版本,其中一个我们常见可直接运行在浏览器的版本 vue.js 就是携带了模板编译代码的完整版,但是不推荐在开发项目的时候使用带编译的版本,因为这个版本确实会比较慢。但是在学习源码的时候一定要使用编译版本,能更好的理解整个流程
(这里是dist目录中构建好的各种版本的 vuejs)
所以上面那个缓存mount的操作就能够理解了,通过构建工具配合不同的配置文件,构建出无编译版本的源码,减少最后打包生成的代码体积
mount 总的流程比较简单,就是通过编译生成好的 render 函数,生成vnode,然后patch(vnode映射成真实dom的过程)
我们熟知的 beforeMount 组件实例化 事件绑定 依赖收集 mounted 等等都发生在这个过程中
最后 patch 生成好的dom替换掉 options.el 节点
最后放一张大致流程图
总结
整个的实例化流程梳理起来还是比较简单,不过细节就比较麻烦了,需要单独去梳理,后面的文章就慢慢的来梳理细节
发表回复