vue2 框架运行原理剖析系列(二)之 组件挂载$mount神秘之旅!!!
一、vue组件挂载
1.1 上一篇文章中,介绍到组件执行 mountComponent 函数,本文对此展开详细的讲解。
1.2 调用改方法的位置在于entry-runtime-with-compiler.js 的Vue.prototype.$mount,具体代码如下:
其中,
(1)el 是 实例化中,传入构造函数的el 选项,且options.render 的值不存在
(2)如果构造函数中,传入了template 选项,则template 的值就是把这个值处理成对应的template。
(3)如果template 不存在,其次会判断el 选项是否存在,如果存在,则根据文档文字,找到指定的标签包裹的HTML,作为template。
(4)以上方法在实例化 vue 中,存在el 选项的情况下执行,如果我们在开发过程中,没有使用这样方式创建,而是创建完之后,再挂载,也是可以的,这样就是用户手动触发该挂载函数,
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean
): Component {debuggerel = el && query(el)//执行$mount 函数,会判断是否存在render 函数,不存在在则将template编译成渲染函数,把render赋值为改渲染函数const options = this.$options// resolve template/el and convert to render functionif (!options.render) {let template = options.templateif (template) {if (typeof template === 'string') {if (template.charAt(0) === '#') {template = idToTemplate(template)/* istanbul ignore if */}} else if (template.nodeType) {template = template.innerHTML} else {return this}} else if (el) {template = getOuterHTML(el)}if (template) {/* istanbul ignore if */const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns}}return mount.call(this, el, hydrating)
}
1.3 代码的解释可以对应到vue官网的生命周期示例图

1.4 是否存在el 的选项,实际上是在 init 函数的时候判断的,代码如下:
//文件位置 src/core/instance/init.js
if (vm.$options.el) {vm.$mount(vm.$options.el)}
1.5 挂载vue 的两种方式如下:
(1)用户手动触发 挂载函数,就是对于上图的对应的没有el 选项的情况
const app= new Vue();
app.$mount('.todoapp')
(2)实例化过程传入el选项
const app= new Vue({el:".todoapp"});
这两种方式实际上都会调用同一个方法,就是上面说的挂载函数,还会执行同样的判断逻辑。
二、模板 template 编译生成抽象语法树ast与渲染函数render生成
2.1 在我们实际过程中,我们把vue 的模板语法写到HTML 文件中,这样,如果没有经过特殊处理,浏览器都会把他们当做普通的文本处理,不会显示成我们想要的效果,那么,这一个步骤究竟是怎么实现的,下面我就带大家深入vue的模板编译。
2.2 在 一 中,介绍了template如何获取,那么下面就针对其接下来的要做的工作展开剖析,下一步代码如下:
将 获取到的template 传入 compileToFunctions 函数中 ,得到一个 render 和 staticRenderFns ,
然后将其赋值给 options,这个options 实际上就是指向 构造函数的参数对象
const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns
2.3 那么这个compileToFunctions 函数到底做了什么,其代码如下:
export const createCompiler = createCompilerCreator(function baseCompile (template: string,options: CompilerOptions
): CompiledResult {debuggerconst ast = parse(template.trim(), options)if (options.optimize !== false) {optimize(ast, options)}const code = generate(ast, options)return {ast,render: code.render,staticRenderFns: code.staticRenderFns}
})
2.4 其中,parse() 函数,是将template 编译成抽象语法树,其实际上就是一个对象,根据文档的嵌套结构,使用js 对象和其属性,模拟出来的一个表达其文档信息的对象,如下图所示:

2.5 得到抽象语法树之后,调用 generate(ast, options) 方法,把抽象语法树,作为参数,生成一个 code对象 ,code 具体样子如下:

2.6 其中code对象 render 值,就是一个函数,这个函数其实就是生成一个虚拟DOM 的函数,后面会在 _render 函数中调用这个函数
三、虚拟DOM 与 渲染函数与视图更新
3.1 mountComponent 的代码如下:
其中定义一个 updateComponent 方法用于更新视图,里面调用了vm._render(), 这个方法就是调用了二中的 render 函数,生成一个虚拟DOM ,然后执行vm._update 方法渲染真实的dom。
其中updateComponent 方法触发的机制是 数据方式变化是,由Watcher 调用 updateComponent 方法更新视图
xport function mountComponent (vm: Component,el: ?Element,hydrating?: boolean
): Component {// debuggervm.$el = elcallHook(vm, 'beforeMount')let updateComponent/* istanbul ignore if */updateComponent = () => {vm._update(vm._render(), hydrating)}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 hookif (vm.$vnode == null) {vm._isMounted = truecallHook(vm, 'mounted')}return vm
3.2 vm._update 方法代码如下,其位置在src/core/instance/lifecycle 中,代码如下:
该方法 调用了patch 方法,进行新旧虚拟dom 的对比,然后更新视图,实现视图更新
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {const vm: Component = thisconst prevEl = vm.$elconst prevVnode = vm._vnodeconst restoreActiveInstance = setActiveInstance(vm)vm._vnode = vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if (!prevVnode) {// initial rendervm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)} else {// updatesvm.$el = vm.__patch__(prevVnode, vnode)}restoreActiveInstance()// update __vue__ referenceif (prevEl) {prevEl.__vue__ = null}if (vm.$el) {vm.$el.__vue__ = vm}// if parent is an HOC, update its $el as wellif (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {vm.$parent.$el = vm.$el}// updated hook is called by the scheduler to ensure that children are// updated in a parent's updated hook.}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
