我们在各种初始化都完成的情况下开始Vue挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el) // 调用的是web/entry-runtime-with-compiler.js
}
mount函数
// runtime/index.js 运行时的$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
// entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
const options = this.$options
// 转化template/el 为render函数
if (!options.render) {
let template = options.template
if (template) { // 解析template
if (typeof template === 'string') {
if (template.charAt(0) === '#') { // template='#id'
template = idToTemplate(template) // idToTemplate去查看源代码
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el) // 通过el获取template
}
if (template) {
// 对template-->render
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this) // compileToFunctions去源码查看
options.render = render // // 生成的render函数with(this){return _c("div") }
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating) // 运行是的mount
}
模版编译createCompiler
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 解析ast
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render, // with(this){return _c("div") }
staticRenderFns: code.staticRenderFns
}
})
- parse函数是解析出ast,里面就是正则匹配解析template
- generate根据ast生成render
这里介绍一下ast
declare type ASTElement = {
type: 1;
tag: string;
attrsList: Array<ASTAttr>;
attrsMap: { [key: string]: any };
rawAttrsMap: { [key: string]: ASTAttr };
parent: ASTElement | void;
children: Array<ASTNode>;
start?: number;
end?: number;
processed?: true;
static?: boolean;
staticRoot?: boolean;
staticInFor?: boolean;
staticProcessed?: boolean;
hasBindings?: boolean;
text?: string;
attrs?: Array<ASTAttr>;
dynamicAttrs?: Array<ASTAttr>;
props?: Array<ASTAttr>;
plain?: boolean;
pre?: true;
ns?: string;
component?: string;
inlineTemplate?: true;
transitionMode?: string | null;
slotName?: ?string;
slotTarget?: ?string;
slotTargetDynamic?: boolean;
slotScope?: ?string;
scopedSlots?: { [name: string]: ASTElement };
ref?: string;
refInFor?: boolean;
if?: string;
ifProcessed?: boolean;
elseif?: string;
else?: true;
ifConditions?: ASTIfConditions;
for?: string;
forProcessed?: boolean;
key?: string;
alias?: string;
iterator1?: string;
iterator2?: string;
staticClass?: string;
classBinding?: string;
staticStyle?: string;
styleBinding?: string;
events?: ASTElementHandlers;
nativeEvents?: ASTElementHandlers;
transition?: string | true;
transitionOnAppear?: boolean;
model?: {
value: string;
callback: string;
expression: string;
};
directives?: Array<ASTDirective>;
forbidden?: true;
once?: true;
onceProcessed?: boolean;
wrapData?: (code: string) => string;
wrapListeners?: (code: string) => string;
};
mountComponent
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
...
}
// 必须保证render函数存在
callHook(vm, 'beforeMount') // 这里调用beforeMount钩子
let updateComponent
...
updateComponent = () => {
vm._update(vm._render(), hydrating) // 先调用_render,在调用_update
}
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */) // RenderWatcher
hydrating = false
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted') // 调用mounted
}
return vm
}
关于挂载组件最好是自己去单步调试,查看一下细节,这里做一下简单介绍
- 先调用_render函数拿到vnode
- 在调用_update函数
- 在调用 patch函数
- 先 createElm(vnode, insertedVnodeQueue)
- 最后在 removeVnodes([oldVnode], 0, 0)