Vue 实例挂载方法($mount)的实现
Posted 十木禾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 实例挂载方法($mount)的实现相关的知识,希望对你有一定的参考价值。
在 Vue 的 _init
方法中已经回调了beforeCreate
和created
这两个生命周期钩子,在此之后就进行了实例的挂载
if (vm.$options.el) // 挂载实例
vm.$mount(vm.$options.el);
在挂载函数中,将要进行 beforeMount
和 mounted
的回调。
在不同的平台下对于 $mount
函数的实现是有差异的,下面考虑 web 平台的 runtime-with-compiler
版本 , 其在web
平台下的定义如下(src/platforms/web/runtime/index.js
)
import mountComponent from 'core/instance/lifecycle';
Vue.prototype.$mount = function(
el?: string | Element,
hydrating?: boolean
): Component
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating);
;
在$mount
函数的参数中,第一个为我们属性的el
, 第二个参数为服务端渲染有关,在patch
函数中用到,这里可以忽略。
但是在调用这个$mount
函数的时候,首先调用的是不同版本下的$mount
函数,然后在该函数中再调用相应平台的$mount
函数,如下在 runtime-with-compiler
版本中$mount
函数如下(src/platforms/web/entry-runtime-with-compiler.js
)
import Vue from './runtime/index';
const mount = Vue.prototype.$mount; // 缓存 上面的 $mount 方法
Vue.prototype.$mount = function(
el?: string | Element,
hydrating?: boolean
): Component
el = el && query(el);
// 不能挂载到 body 和 html 上
if (el === document.body || el === document.documentElement)
return this;
const options = this.$options;
if (!options.render) // 如果没有 render 函数
// ... 将 render 函数添加到 options 上
const render, staticRenderFns = compileToFunctions(template,
outputSourceRange : process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters : options.delimiters,
comments : options.comments,
, this);
options.render = render;
options.staticRenderFns = staticRenderFns;
// ...
return mount.call(this, el, hydrating);
;
可知该函数主要干了三件事
- 由于挂载之后会替换被挂载的对象,所以限制不能挂载到
body
和html
上 - 如果当前Vue实例没有
render()
函数(写template
等),则通过编译等手段,将render
函数添加到options
上 - 调用在代码开头我们先缓存的
$mount
方法,该方法就是web
平台下的方法。
在web
平台下的$mount
方法里面主要就是调用了mountComponent()
方法,接下来我们的核心就是该方法了
在'core/instance/lifecycle.js
文件中我们找到了该方法的定义,删掉一些非重点代码后如下
export function mountComponent(
vm: Component,
el: ?Element,
hydrating?: boolean
): Component
vm.$el = el;
if (!vm.$options.render)
// 不是重点,该处主要是用来对没有 render 函数下的一些错误提示
callHook(vm, 'beforeMount'); // 回调 beforeMount , 开始准备挂载实例
// 声明 更新组件 的函数 (源代码中有关performance配置不是重点,故省略)
const updateComponent = updateComponent = () =>
vm._update(vm._render(), hydrating);
;
// new 一个 Watcher [isRenderWatcher]
new Watcher(vm, updateComponent, noop,
before()
if (vm._isMounted && !vm._isDestroyed)
callHook(vm, 'beforeUpdate');
,
, true /* isRenderWatcher */);
hydrating = false;
// Vue 的根实例的 mounted 回调在这里执行
if (vm.$vnode == null)
vm._isMounted = true;
callHook(vm, 'mounted');
return vm;
上面的代码中主要干了如下三件事
- 回调
beforeMount
- 生成
updateComponent
方法,该方法将vnode
渲染为真实的DOM
- new 一个
Watcher
,并在该Watcher
在调用updateComponent
方法 - 回调
mounted
对于 updateComponent
方法较为复杂,其内部主要调用_update()
将 vnode
渲染为浏览器上显示的真实DOM
我们考虑如下两个问题
1. Watcher
中如何调用 updateComponent
方法
Watcher
函数的构造函数接受如下的参数
constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
)
在上面的代码中,updateComponent()
方法作为第二个参数传递过来,即构造函数中的expOrFn
往下看会看到
if (typeof expOrFn === 'function')
this.getter = expOrFn;
也就是说updateComponent()
方法被设置为了getter()
方法
看到构造函数的最后
this.value = this.lazy
? undefined
: this.get();
其中 lazy
属性的值在前面被设置为了 false
this.lazy = !!options.lazy; // 我们 options 中没有 lazy 属性
这也就是说,咋i构造函数的末尾会调用this.get()
,而在this.get()
中
const vm = this.vm;
try
value = this.getter.call(vm, vm);
我们看到调用了getter()
方法,也就是调用了updateComponent()
方法。
2. 为什么根实例的$vnode
为空
在initRender()
函数中有如下代码
const parentVnode = vm.$vnode = options._parentVnode;
也就是说 当前实际的 $vnode
值为其父节点的vnode
值
而根实例没有父节点,故其$vnode
值就为空了,所以会执行
if (vm.$vnode == null)
vm._isMounted = true;
callHook(vm, 'mounted');
那么子节点的mounted
回调是在那里执行的呢?
在 path()
(core/vdom/patch.js
) 函数中有如下代码
function invokeInsertHook(vnode, queue, initial)
if (isTrue(initial) && isDef(vnode.parent))
vnode.parent.data.pendingInsert = queue;
else
for (let i = 0; i < queue.length; ++i)
queue[i].data.hook.insert(queue[i]); // 这里
在循环queue
的时候,调用了 insert()
方法,该方法为 VNodeHooks
,其在componentVNodeHooks
(core/vdom/create-component.js
)中声明,代码如下
const componentVNodeHooks =
insert(vnode: MountedComponentVNode)
const context, componentInstance = vnode;
if (!componentInstance._isMounted)
componentInstance._isMounted = true;
callHook(componentInstance, 'mounted'); // 这里
if (vnode.data.keepAlive)
if (context._isMounted)
queueActivatedComponent(componentInstance);
else
activateChildComponent(componentInstance, true /* direct */);
,
由于 path()
方法在 _update()
函数中调用,这部不再重点说明。
下节我们将来说说render()
和 _update()
方法的实现
欢迎关注微信公众号,会有最新的文章哦~: 子曰思鱼
以上是关于Vue 实例挂载方法($mount)的实现的主要内容,如果未能解决你的问题,请参考以下文章