打包构建
观察项目的 package.json 文件中的 scripts 字段,我们以 npm run dev 为例,当运行该命令时,会执行 scripts/config.js 脚本。
该脚本中定义了很多种构建目标,比如我们要分析的 web-full-dev,它的构建产物包括了运行时和编译器。注意到 entry 这个字段 web/entry-runtime-with-compiler.js,它表示的是构建的入口文件,我们来看看该文件主要做了什么。
该文件中最重要的工作之一是重写了 $mount 方法:
1 | const mount = Vue.prototype.$mount |
而该函数中则主要是为了将 Vue 实例中的 template 转换成 render 函数:
1 | const {render, staticRenderFns} = compileToFunctions( |
该文件头部还引入了 ./runtime/index,我们顺藤摸瓜,来看看该文件:
1 | import Vue from 'core/index' |
这里做了几件事:
- 引入了 Vue
- 定义了平台相关的
__patch__方法 - 定义了
$mount函数
我们再来看一下 core/index:
1 | import Vue from './instance/index' |
这里会初始化一些全局的 api:
1 | Vue.util = { |
再来看一下 instance/index.js:
1 | import {initMixin} from './init' |
这里主要是在原型上挂载一些方法。然后,终于我们的主角 Vue 现身了!
首次渲染流程
先看一个简单的例子:
1 | <div id="demo"></div> |
该例子做了两件事:
- 实例化一个
Vue - 将其挂载到 id 为
demo的元素上
下面我们来看看这两步。
实例化
当我们实例化一个 Vue 对象时,我们会调用 _init 方法:
1 | initLifecycle(vm) // $parent $root $children |
挂载
entry-runtime-with-compiler.js 中对 $mount 方法进行了装饰,主要是为了把模板解析成 render 函数。这里就赋予了 vue 具有跨平台的能力,不同的平台只要实现自己平台下模板的转换即可。
1 | import Vue from './runtime/index' |
我们来看看 mount,它位于 platforms/web/runtime/index.js:
1 | Vue.prototype.$mount = function ( |
最后是调用了 mountComponent (core/instance/lifecycle):
1 | export function mountComponent( |
这里创建了一个 updateComponent 函数,并把它传递给了 new Watcher()。后面我们会讲到每个自定义组件都会执行 $mount,最终都会走到这里,即每个组件会对应一个 watcher。在 vue1 的时候,视图的更新粒度是非常小的,而且也不需要虚拟 dom 和 diff 算法,但是这样会导致一个组件中会出现很多个 watcher。vue2 为了避免出现这种情况,把更新粒度给增大了,所以现在就需要 vdom 和 diff 算法来精确的知道需要更新哪里了。
Watcher 的细节我们先不管,它里面肯定会去执行 updateComponent,最后会执行 _render 和 _update,我们先来看看 _render:
1 | Vue.prototype._render = function (): VNode { |
这里主要是执行 Vue 实例的 render 函数,生成 vnode,vnode 会作为参数传给 _update:
1 | Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { |
这里主要是调用了 vm.__patch__,它来自平台相关的目录 platforms/runtime/patch.js:
1 | import {createPatchFunction} from 'core/vdom/patch' |
它只是把一些平台相关的配置传递给了 createPatchFunction,该函数执行后返回一个 patch 函数,vm.__patch__ 真正执行的是这个函数(这个函数有点复杂,我们先不展开):
1 | return function patch(oldVnode, vnode, hydrating, removeOnly) {} |
到这里,我们的首次渲染就快速走读完了。
