- vue组件实例在初始化完成各种状态数据后,会触发vm.$mount()方法来进行模板编译阶段,有两种触发方式
javascript
// 方法一:主动触发 new Vue({ el: '#app' })
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
// 方法二:手动调用 new Vue().$mount("#app")
- 生成render渲染函数:执行$mount(el)方法,获取dom元素,判断vm.options中是否有render函数,没有的话需要先获取template的HTML片段,再执行compileToFunctions方法生成render函数。拿到render函数后接着触发mountComponent函数的执行
javascript
Vue.prototype.$mount = function (el) {
// 获取dom
el = el && query(el);
let options = this.$options;
// 没有render函数,则需要解析 模板/el 并生成render函数
if (!options.render) {
// 获取模板字符串
let template = options.template;
if (template) {
if (typeof template === "string") {
// 模板第一个字符串为# 则判断该字符串为dom的id
if (template.charAt(0) === "#") {
template = document.querySelector(template).innerHTML;
}
} else if (template.nodeType) {
// template是dom元素
template = template.innerHTML;
}
} else if (el) {
// outerHTML:dom元素的序列化HTML片段
template = el.outerHTML;
}
if (template) {
let _a = compileToFunctions(template, { outputSourceRange: true }, this);
options.render = _a.render;
options.staticRenderFns = _a.staticRenderFns;
}
}
return mountComponent(this, el, false);
};
// 获取dom元素
function query(el) {
if (typeof el === "string") {
var selected = document.querySelector(el);
if (!selected) {
return document.createElement("div");
}
return selected;
} else {
return el;
}
}
- compileToFunctions方法中,生成render函数的步骤:①.parseHTML方法生成ast语法树,主要是利用正则与字符串方法循环处理HTML片段;②.generate(ast,options)方法生成render函数,主要是将ast语法树转换成render渲染函数,源码看不动了没贴
javascript
let cache = Object.create(null);
function compileToFunctions(template, options, vm) {
if (cache[template]) {
return cache[template];
}
let compiled = compile(template, options);
let res = {};
let fnGenErrors = [];
res.render = createFunction(compiled.render, fnGenErrors);
res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
return createFunction(code, fnGenErrors);
});
return (cache[key] = res);
}
let baseOptions = {
expectHTML: true,
modules: modules,
directives: directives,
isPreTag: isPreTag,
isUnaryTag: isUnaryTag,
mustUseProp: mustUseProp,
canBeLeftOpenTag: canBeLeftOpenTag,
isReservedTag: isReservedTag,
getTagNamespace: getTagNamespace,
staticKeys: genStaticKeys$1(modules)
};
function compile(template, options) {
let finalOptions = Object.create(baseOptions);
if (options) {
for (let key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key];
}
}
}
let compiled = baseCompile(template.trim(), finalOptions);
return compiled;
}
// ......
- 执行mountComponent方法,首先是生成el,接着执行beforeMount生命周期中的函数,接着生成渲染watcher实例,主动触发watcher实例的getter方法,getter方法就是updateComponent。 在vm._render方法中生成了**虚拟dom** (vnode),在生成虚拟dom的过程中触发了render函数,在读取模板中的响应式数据时进而实现了响应式数据的依赖收集。 在vm._update方法中将虚拟dom转成真实dom节点,实现了**DOM的挂载**,并赋值给vm.el,核心方法是patch()方法
javascript
function mountComponent(vm, el, hydrating) {
vm.$el = el;
callHook$1(vm, 'beforeMount');
let updateComponent = function () {
vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, watcherOptions, true);
}
function _update(vnode) {
if (!prevVnode) {
// initial render
vm.$el = patch(vm.$el, vnode, hydrating, false /* removeOnly */);
}
else {
// updates
vm.$el = patch(prevVnode, vnode);
}
}
- 接着执行callHook$1(vm, 'mounted')方法,触发mounted生命周期中的函数执行
javascript
function mountComponent(vm, el, hydrating) {
// ...
new Watcher(vm, updateComponent, noop, watcherOptions, true);
callHook$1(vm, 'mounted');
}
模板编译:
- 获取template HTML片段,根据HTML片段生成ast语法树;
- 根据ast语法树生成rende渲染函数;
- 执行render函数生成虚拟dom(vnode);
- 将虚拟dom转成真实dom节点并挂载在页面上;