VUE源码解析
flow类型检查
Vue.js 的源码使用了Flow做静态类型检查,之所以选择Flow , 是因为 Babel和 ESlint 都有对应的Flow插件支持语法。
目录源码解析
compiler 目录包含Vue.js 所有编译相关的代码。它包括把模板解析成AST语法树,AST语法树优化,代码生成等功能
core 目录包含了Vue.js的核心代码,包括内置组件、全局API封装、Vue实例化、观察者、虚拟DOM、工具函数等
platfrom Vue.js是一个跨平台的MVVM框架,可以跑在web 或者配合weex 跑在natvie客户端上。这里两个目录对应两个平台的打包入口
server 目录包含了服务端渲染的逻辑,这部分代码是跑在服务器的node.js
sfc 目录包含了把 .vue 文件内容解析成一个 javascript 对象
shared 目录包含了一些定义的工具方法,这些方法会被浏览器端的Vue.js 和服务端的Vue.js 共同使用
Vue的初始化过程
Vue的本质上就是一个用Function实现的Class类,然后它的原型prototypey以及它本身都扩展了一系列的方法和属性。
在 import Vue 的时候,会执行core 文件夹中的 index.js , 通过 instance文件夹中的index.js文件给Vue的prototype上扩展一些方法;通过 initGlobalAPI 方法扩展 nextTick 、set、del 等全局静态方法。
new Vue的时候,会调用 this._init 方法, 通过该方法,合并配置、初始化生命周期、初始化事件中心、初始化渲染、初始化data 、props、computed、watcher 等等。初始化最后,检测到如果有el 属性,则调用 vm.$mount 方法挂载vm, 把模板渲染成最终的DOM 。
$mount 方法 先对 el 做了限制 , Vue 不能挂载在 body 、html 这样的根节点上。Vue组件的最后渲染都需要render方法,如果没有定义render 方法,则会把 el 或者 template 字符串转换成 render 方法,这个过程是Vue的一个在线编译过程,它是通过 compileToFunctions 方法实现的。
$mount 方法支持传入两个参数,第一个是 el ,表示挂载的元素,可以是字符串,也可以是DOM对象,如果是字符串在浏览器环境下会调用 query 方法转换成DOM对象;第二个参数是和服务端渲染相关。
$mount 方法实际上调用的是 mountComponent 方法,mountComponent 方法的核心是先调用 vm._render 方法生成虚拟DOM , 再实例化一个渲染 Watcher (观察者) , 在它的回调函数中会调用 updateComponent 方法,最终调用vm._update更新DOM 。
Watcher 在这里的作用,一是初始化的时候会执行回调函数,二是当 vm实例中的监测的数据发生变化的时候执行回调函数。函数最后判断为根节点的时候设置 vm._isMounted 为 true , 表示这个实例已经挂载了,同时执行 mounted 钩子函数。 vm.$vnode 表示 Vue实例的父虚拟 Node , 为 null 时表示当前是 根Vue 的实例。
vm._render 方法最终是通过执行 createElement 方法并返回 vnode , 它是一个虚拟 node ,接下来通过 vm._update 方法渲染成一个真实的DOM并渲染出来
vm._update 被调用的时机有两个,一是首次渲染,二是数据更新的时候。vm._update的核心是调用 vm._patch_ 方法,这个方法在web 和 weex 平台上的定义是不一样的。 该方法内部 调用 insert 方法把DOM插入父节点中。
patch的整体流程:createComponent -> 子组件初始化 -> 子组件render -> 子组件patch 嵌套组件的插入顺序是先子后父
Vue各组件间合并配置过程:对于options的合并方式有2种,子组件初始化过程通过 initInternalComponent 方式要比外部初始化Vue通过 mergeOptions 的过程要快,合并完的结果保留在 vm.$options 中。
源码中最终执行生命周期的函数都是调用 callHook 方法
异步组件
Vue的异步组件有3种方式,高级异步组件实现了 loading 、resolve、reject、timeout 4种状态,异步组件实现的本质是2次渲染,除了0 delay的高级异步组件第一次直接渲染成 loading组件外,其他都是第一次渲染生成一个注释节点,当异步获取组件成功后,再通过 forceRender 强制重新渲染。
依赖收集/派发更新
收集依赖的目的是为了当这些响应式数据发生变化,触发它们的 setter 的时候,能知道应该通知哪些订阅者去做相应的逻辑处理,我们把这个过程叫做派发更新。
当数据发生变化时,触发 setter 逻辑,把在依赖过程中订阅的所有观察者,也就是 wather,都触发它们的 update 过程,这个过程又利用队列做了进一步优化,在 nextTick 后执行所有 watcher 的 run ,最后执行它们的回调函数。
计算属性/侦听属性
计算属性的本质是 computed watcher
侦听属性的本质是 user watcher,它还支持deep、sync、immediate等配置
组件更新
组件更新的过程就是新旧 vnode diff , 对新旧节点相同以及不同的情况分别做不同的处理。
新旧节点不同的更新流程是 创建新节点 -> 更新父占位符节点 -> 删除旧节点 ;
新旧节点相同的更新流程是去获取它们的 childern , 根据不同情况做不同的更新逻辑。
新旧节点相同且它们都存在子节点,那么会执行 updateChildren 逻辑 。