上一篇中,咱们一起探讨了 new Vue({…})背地产生了什么。那么当咱们实例化 vue 之后,进行 dom 挂载又产生了什么呢?
仔细的同学会发现:$mount 办法在多个文件中被定义,如:
- src/platform/web/entry-runtime-with-compiler.js
- src/platform/web/runtime/index.js
- src/platform/weex/runtime/index.js
之所以有多个中央,是因为 $mount 实现是和平台、构建形式都相干的
上面,咱们抉择 compiler 版本剖析
一. $mount 骨干代码如下:
Vue.prototype.$mount = function(el?: string | Element, hydrating?: boolean): Component {el = el && query(el)
// query 办法,实际上是对 el 参数做了一个转化,el 可能是 string 或者 element。如果是 string,将返回 document.querySelector(el)
// ...
const options = this.$options
if (!options.render) {
// render 函数不存在
let template = options.template
if (template) {
// 如果存在 template 配置项:// 1. template 可能是 "#xx",那么依据 id 获取 element 内容
// 2. 如果 template 存在 nodeType,那么获取 template.innerHTML 内容
}else {
// 如果 template 配置项不存在 template,然而存在 el:
/*
* 例如:new Vue({
* el: "#app",
* ...
* })
*
*/
// 那么依据 el 获取对应的 element 内容
}
// 通过下面的解决,将获取的 template 做为参数调用 compileToFunctions 办法
// compileToFunctions 办法会返回 render 函数办法,render 办法会保留到 vm.$options 上面
const {render, staticRenderFns} = compileToFunctions(template, {...})
options.render = render
}
return mount.call(this, el, hydrating)
}
从骨干代码咱们能够看出做了以下几件事
- 因为 el 参数有两种类型,可能是 string 或者 element,调用 query 办法,对立转化为 Element 类型
- 如果没有手写 render 函数,那么先获取 template 内容。再将 template 做为参数,调用 compileToFunctions 办法,返回 render 函数。
- 最初调用 mount.call,这个办法实际上会调用 runtime/index.js 的 mount 办法
注:
- vue compiler 别离 2 个版本:一个是构建时版本,即咱们应用 vue-loader + webpack。另一个版本是:运行时版本,运行的时候,再去 compiler 解析。咱们这里剖析的是 运行时
- vue 最终只认 render 函数,所以如果咱们手动写 render 函数,那么就间接调用 mount.call。反之,vue 会将 template 做为参数,运行时调用 compileToFunctions 办法,转化为 render 函数,再去调用 mount.call 办法。
- 如果是构建时版本,vue-loader + webpack,会先将咱们本地的代码转化成 render 函数,运行将间接调用 mount.call。生产环境,咱们举荐构建时的版本。集体学习举荐运行时版本。
- mount.call 办法,实际上会调用 runtime/index.js 上面的 $mount 办法,而这个办法很简略,将会调用 mountComponent 办法。
二. mountComponent 骨干代码如下:
export function mountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component {
// ...
// 调用 beforeMount 生命周期函数
callHook(vm, 'beforeMount')
// ...
// 定义 updateComonent 办法
let updateComponent = () => {vm._update(vm._render(), hydrating)
}
// ...
new Watcher(vm, updateComponent, noop, {before () {if (vm._isMounted && !vm._isDestroyed) {callHook(vm, 'beforeUpdate')
}
}
}, true)
// ...
// 调用生命周期函数 mounted
callHook(vm, 'mounted')
}
Watch 类相干代码
Watch 类有许多逻辑,这里咱们只看和 $mount 相干的:
class Watcher {
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
){
// ...
if (typeof expOrFn === 'function') {this.getter = expOrFn}else {// ...}
// ...
this.value = this.lazy ? undefined : this.get()}
get() {
// ...
value = this.getter.call(vm, vm)
// ...
// cleanupDeps 办法前面咱们会剖析,这个在性能优化上比拟重要
return value;
}
}
从下面代码,能够看出:
- 先调用 beforeMount 钩子函数
- 将 updateComponent 办法做为参数,实例化 Watch。Watch 在这个有 2 个作用:
1、初始化的时候会执行回调函数
2、当 vm 实例中的监测的数据发生变化的时候执行回调函数
这里,咱们先看第 1 个。第 2 个将在数据变动监测章节剖析
执行回调后,咱们看到 vm._update(vm._render(), hydrating)办法,这个办法分 2 个步骤:
(1) 执行 render 办法,返回最新的 VNode 节点树
(2) 调用 update 办法,实际上进行 diff 算法比拟,实现一次渲染 - 调用 mounted 钩子函数
三. 总结
- options 上无 render 函数,对 template, el 做解决,获取 template 内容。
- 调用 compileToFunctions 办法,获取 render 函数,增加到 options.render 上
- 调用 mount.call,实际上是调用 mountComponent 函数
- 调用 beforeMount 钩子
- 实例化渲染 watcher,执行回调
- 依据 render 函数获取 VNode 节点树(其实是一个 js 对象)
- 执行 update 办法,实际上是 patch 过程,vue 会执行 diff 算法,实现一次渲染
- 调用 mounted 钩子
在上面的章节,咱们将陆续剖析: 响应式,compileToFunctions, 虚构 DOM,以及 patch
码字不易,多多关注~????