开始
上篇说到vue的_init函数,在改函数的最后有一个$mount 函数
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
说$mount之前先说下编译,可以看到plantforms平台下面的几个入口文件。这个是打包编译的入口,和我们相关的三个文件。entry-compiler.js就是编译的入口文件,简单的说就是把我们写的.vue文件的template这种格式的进行编译,编译成render类型的函数,类似这样的。
render: function (createElement) {
return createElement(
'h1', // 标签名称
12312312 // 子节点数组
)
},
而entry-runtime.js文件就是我们直接运行编译好render函数这种函数的方式的文件,而entry-runtime-with-compiler.js就是两者结合,现编译再运行的文件入口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F9JTlDby-1656482098753)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/18ff3703c89b43559968a72bfa881098~tplv-k3u1fbpfcp-zoom-in-crop-mark:1956:0:0:0.image?)]
就看比较简单的runtime.js文件吧,可以发现找到了src/platform/web/runtime/index.js
,其中就有一个$mount函数,也绑定再了vue的原型上了,而执行的mountComponent
就是src/core/instance/lifecycle
中的mountComponent
。
.....
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
截取了比较重要的一段。首先限制性了beforeMount这个钩子函数,然后有两个比较重要的函数_update,_render
两个函数,这边主要看_render,这个函数是在renderMixin的时候绑定的, 这边看到这个 vnode = render.call(vm._renderProxy, vm.$createElement)
这个就是我们再render方法中直接执行render中方法,,再看 vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
可以看到,render 函数中的 createElement
方法就是 vm.$createElement 方法。这边 vm._render
最终是通过执行 createElement
方法并返回的是 vnode
,继续下去看creteElement
。
export function _createElement (
context: Component,
tag?: string | Class<Component> | Function | Object,
data?: VNodeData,
children?: any,
normalizationType?: number
): VNode | Array<VNode> {
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
if (!__WEEX__ || !('@binding' in data.key)) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
) {
data = data || {}
data.scopedSlots = { default: children[0] }
children.length = 0
}
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
warn(
`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
context
)
}
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 {
// unknown or unlisted namespaced elements
// check at runtime because it may get assigned a namespace when its
// parent normalizes children
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()
}
}
很长一串,其实都是做了一些判断,真正核心的语句就是创建VNODE,
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
这边就讲到了虚拟DOM,这个下面会讲。再回到mountComponent方法执行了Watch这个实例,后面再讲watcher。执行了beforeUpdate这个生命周期函数和mounted这个生命周期函数。至此$mout这个函数就完成了。
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
总结
看源码的时候不要一个一个的仔细去看每个方法里面执行的过程。大概知道这个函数是干嘛的,先看个大概流程,边看边猜,最后再回过头把猜的那一部分仔细阅读,脑子里要有个大概的流程,琢磨久了,很多之前不理解的问题也就迎刃而解了。