打包构建
观察项目的 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) {} |
到这里,我们的首次渲染就快速走读完了。