今天我们来说下vue实例的$mount中都发生了什么。$mount是Vue原型上的方法,是Vue实例化的最后一步。$mount分为带编译器版本和不带编译器版本。我们以下面的代码为例,来讲下在$mount时都发生了什么。
实例代码如下(来源于codesandbox的默认vue项目代码):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: h => h(App)
}).$mount("#app");
// app.vue
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
components: {
HelloWorld
}
};
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
// HelloWorld.vue
export default {
name: "HelloWorld",
props: {
msg: String
}
};
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
$mount是vue实例化中不可缺少的一部分,它将template转化成虚拟dom,然后根据虚拟dom渲染真实dom节点并进行挂载。在这个过程中,主要讲以下几点:
mountComponent中都发生了什么
渲染watcher(renderWatcher)的执行过程
entry-runtime.js和entry-runtime-with-compiler.js的区别及适用场景
1. mountComponent中发生了什么?
在Vue实例化完成的最后一步(即_init原型方法中),如果初始化的参数里有el,则自动调用$mount方法。否则,需要手动调用$mount方法并传入挂载节点。
实例代码中,是在new Vue()之后手动调用了$mount方法,并传入了App组件。下面找到$mount方法的定义:
1
2
3
4
5
6
7
8
9
10
11
12// src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): {
el = el && inBrowser ? query(el) : undefined
/**
* el: {name: "App", components: {…}, render: ƒ, staticRenderFns: Array(0), _compiled: true, ...rest}
**/
return mountComponent(this, el, hydrating)
}
由上可知,$mount获取到传入的App, 并且去query。在query中,由于App经过vue-loader编译后是一个Object,所以在query中被直接返回了。所以然后紧接着调用mountComponent函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56// src/core/instance
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): {
vm.$el = el
// 1. 检查vm.$options.render是否存在,如果不存在,给出警告
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
// 2. 调用beforeMount生命周期钩子
callHook(vm, 'beforeMount')
let updateComponent
updateComponent = () => {
// mountComponent的核心
vm._update(vm._render(), hydrating)
}
// 3. 生成渲染watcher
new Watcher(vm, updateComponent, noop, {
before () {
// 如果当前vm实例已经被挂载,并且没有被销毁,调用beforeMount生命周期钩子
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
在mountComponent中,renderWatcher是最关键的地方。通过renderWatcher,将render函数转换成vnode,然后通过vm._update,将vnode渲染成真实的dom。
2. 渲染watcher(renderWatcher)的执行过程
渲染watcher的执行过程,就是Watcher实例化的过程。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54// src/core/observer/watcher.js
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// 当前isrenderwatcher为true
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.before = options.before // 这个函数会选择性调用beforeUpdate钩子函数
} else {
this.deep = this.user = this.lazy = this.sync = false
}
// expOrFn 就是updateComponent函数。
if (typeof expOrFn === 'function') {
this.getter = expOrFn // 会走到这里啦
}
this.value = this.lazy // lazy是false,所以会调用this.get函数。
? undefined
: this.get()
}
get () {
pushTarget(this) // 将当前的Dep.target设置为该renderWatcher。
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm) // 在这里会调用updateComponent,即vm._update(vm._render(), hydrating);
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value)
}
popTarget() // getter执行完毕后把Dep.target设置为之前的watcher实例。
this.cleanupDeps()
}
return value
}
}
由以上代码可知,在renderWatcher的执行过程中,this.get()的执行是最为重要的。而this.get()的执行则主要切换了Dep.target,执行了updateComponent函数。
updateComponent中, 主要执行了vm._update(vm._render(), hydrating)函数。下面着重来分析vm._update的过程。
vm._render的执行过程
_render是Vue原型上的方法,定义在src/core/instance/render.js中,下面分析下_render方法主要做了什么。
其实_render主要做了一件事,调用render函数生成vnode(虚拟dom)并返回。
1. 从vm.$options取出render和_parentVnode。在上边的例子中,new Vue({ render: h => h(App) }).$mount('#app')。
在这里, _parentVnode为undefined。
2. 设置_parentVnode为vm.$vnode,即当前vue实例的占位符vnode。
3. 设置currentRenderingInstance = vm, 同时执行渲染函数render,取得render返回的vnode。
4. 然后将currentRenderingInstance置为null
5. 返回vnode,并设置vnode.parent为_parentVnode.
在这里render函数主要调用了 render.call(vm, vm.$createElement)。对应于例子中, h就是vm.$createElement。就是根据传入的tag或者component,生成对应的vnode的一个函数。下面来分析下vm.$createElement函数。 vm.$createElement也定义在render.js文件中,在vm._init中执行挂载。vm.$createElement定义如下:
1
2
3
4
5// 最后一个true用以标示是用户主动调用,即定义了render函数。而非由编译器调用。
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 在例子中,传入的参数如下:
vm.$createElement(App);
createElement最终会调用_createElement,下面来分析下_createElement。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63// src/core/vdom/create-element.js
export function _createElement (
context: Component,
tag?: string | Class | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array {
// 如果传入的data是一个响应式数据,直接创建并返回空vnode
if (isDef(data) && isDef((data: any).__ob__)) {
return createEmptyVNode()
}
// 处理动态组件情况
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
return createEmptyVNode()
}
// ... 有删减
// 在例子中,这两个值是相等的。处理过程在createElement中。
if (normalizationType === ALWAYS_NORMALIZE) {
// 处理children, 最终生成一个[vnode, vnode, vnode]数组。
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
// 此时tag为对象App
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {
vnode = new VNode(
tag, data, children,
undefined, undefined, context
)
}
} else {
// direct component options / constructor
// 直接进入到这个分支
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
接下来会调用createComponent函数来创建组件。在createComponent中,主要做了下面几件事:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
大专栏
标签:Vue,render,dom,mount,vm,vnode,源码,._,data
来源: https://www.cnblogs.com/liuzhongrong/p/11924652.html