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 节点

最后放一张大致流程图

总结

整个的实例化流程梳理起来还是比较简单,不过细节就比较麻烦了,需要单独去梳理,后面的文章就慢慢的来梳理细节

 

 

 

 

评论

0条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注