new Vue 发生了什么
- 从上一篇文章可知,
vue
其实就是一个function
实现的类. 在new
实例化时它调用了一个方法
function Vue (options) {
this._init(options)
}
this._init()
方法在src/core/instance/init.js
中定义
Vue.prototype._init = function(options?: Object) {
const vm: Component = this;
// a uid
vm._uid = uid++;
let startTag, endTag;
// a flag to avoid this being observed
vm._isVue = true;
// merge options
if (options && options._isComponent) {
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== "production") {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
// expose real self
vm._self = vm;
initLifecycle(vm); // 初始化$parent, $root, $children, $refs
initEvents(vm); // 处理父组件传递的监听器
initRender(vm); // $slots, $scopedSlots,_c(), $createElement()
callHook(vm, "beforeCreate");
initInjections(vm); // 获取注入数据
initState(vm); // 初始化组件中props、methods、data、computed、watch
initProvide(vm); // 提供数据
callHook(vm, "created");
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
}
- 上面代码把不同的功能逻辑拆成一些单独的函数执行。让人看起来一目了然
- 并且在初始化的最后,判断是否有传入el。如果有,则调用$mount进行挂载。
Vue 实例挂载的实现
在Vue
中我们是通过$mount
进行挂载的。$mount
在多个文件都有定义。以下是以带编译版本的$mount
的分析。
- src/platform/web/runtime/index.js
// public mount method
Vue.prototype.$mount = function(
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating);
};
这里是 $mount
方法的定义。传入2个参数,第一个是 el
,表示挂载的元素,可以是字符串和 DOM
元素。如果是字符串在浏览器环境下会调用 query
方法转换成 DOM
对象的。第二个参数是和服务端渲染相关,在浏览器环境下我们不需要传第二个参数。
之所以这么设计,是为了可以复用。因为它是可以被 runtime only
版本的 Vue
直接使用的。
这里实际调用的是 mountComponent
方法
- 方法定义在 src/core/instance/lifecycle.js
export function mountComponent(
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el;
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
}
callHook(vm, "beforeMount");
let updateComponent;
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
updateComponent = () => {
...省略
} else {
// 定义组件更新函数
// _render()执行可以获得虚拟dom,VNode
// _update()将虚拟dom转换为真实dom
updateComponent = () => {
vm._update(vm._render(), hydrating);
};
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate");
}
}
},
true /* isRenderWatcher */
);
hydrating = false;
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true;
callHook(vm, "mounted");
}
return vm;
}
这里现时定义了 updateComponent
方法。其中有两个关键参数
_render
: 执行可以获得虚拟 VNode
;
_update
将虚拟 dom
转换为真实 dom
最后 new
了一个 Watcher
, 并在其回调中传入 updateComponent
方法。
Watcher
类在这里起到两个作用, 一是在初始化时执行回调函数, 二是监听 vm
实例中数据发生变化时执行回调函数
- src/platform/web/entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount;
// 主要是扩展了$mount方法
Vue.prototype.$mount = function(
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el);
// 处理el和template
const options = this.$options;
// resolve template/el and convert to render function
// render不存在时才考虑el和template
if (!options.render) {
let template = options.template;
if (template) {
if (typeof template === "string") {
// template是选择器
if (template.charAt(0) === "#") {
template = idToTemplate(template);
}
} else if (template.nodeType) {
// template是dom元素
// template: document.querySelector('#app'),
template = template.innerHTML;
} else {
return this;
}
} else if (el) {
// el作为template
template = getOuterHTML(el);
}
// 编译过程
if (template) {
// 将template字符串转换为render函数
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);
};
由上可得知,上面代码主要是先缓存$mount
, 然后重新定义(拓展) $mount
方法,也就是最初我们说所的可复用。
首先,只有当 render
方法不存在时, 先是判断 $option.template
. 假如是选择器, 则获取其 innerHTML
, 如果是 dom
元素也是获取其 innerHTML
。 否则返回它本身(本身就是字符串)。假如是 el
。 获取的则是它outerHTML
。最终。不管是 el
还是 template
字符串,都会转换成render方法。这是 Vue
的在线编译过程。它是调用 compileToFunctions
方法实现的。
最后调用最初缓存的 mount
进行挂载