Vue源码学习2--$mount的执行流程

主要梳理了一下$mount的执行和构建过程。文章重点有两块:1、公共的 $mount 方法的创建,2、带编译模板的 vue 给 $mount 扩展了编译部分。

上次梳理了vue入口文件的怎么查找,这次看一看$mount。由于后面尚未学习,只简单梳理流程。

1、扩展$mount

首先打开entry-runtime-with-compiler.js文件,这里我们可以看到对$mount的扩展:
在这里插入图片描述
先用 mount 变量存储Vue.prototype.$mount备用,接着重新给Vue.prototype.$mount赋值。注意这里是给$mount添加编译模块,所以不带编译器的vue文件是不会运行这块代码的。
我们可以看到,函数接受两个参数,第一个参数是一个字符串或者一个Dom元素,第二个参数暂时不管。函数内部首先尝试获取Dom元素,其中query函数如下:

export function query (el: string | Element): Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if (!selected) {
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      )
      return document.createElement('div')
    }
    return selected
  } else {
    return el
  }
}

可以看到其实就是做了下判断。如果是字符串则调用querySelector获取Dom,否则直接返回。
接着判断得到的Dom是不是body或者html元素,如果是则抛出警告。并return当前实例。
在这里插入图片描述
然后就是最重要的部分了,我们在new Vue(options)的时候通常会传el或者template,在vue项目中常见的是利用了render函数。它们的顺序是怎样呢?看源码就可以发现,render > template > el。把if判断折叠起来看起来很清晰:
在这里插入图片描述
如果传入了render,则直接调用mount.call,否则判断是否有template,尝试把template转化为render函数。如果没有template则判断是否有el,有的话则调用getOuterHTML得到elouterHTMLgetOuterHTML代码如下:

function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

通过这个函数得到一串字符串,例如,下面这个例子:

<div id="app">
  <span>{{ msg }}</span>
</div>
new Vue({
	el: "#app",
	data: {
		msg: "hello, world!"
	},
	mounted() {
		console.log(this.$el.outerHTML); // <div id="app"><span>hello, world!</span></div>
		console.log(this.$el.innerHTML); // <span>hello, world!</span>
	}
});

因此我们也可以知道getOuterHTML是将el转化成templateelse部分是对el.outerHTML的模拟。因此核心部分是两个if (template)的内容。
先看第一个:

if (template) {
  if (typeof template === 'string') {
    if (template.charAt(0) === '#') {
      template = idToTemplate(template) // 得到 template 的 innerHTML
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && !template) {
        warn(
          `Template element not found or is empty: ${options.template}`,
          this
        )
      }
    }
  } else if (template.nodeType) {
    template = template.innerHTML
  } else {
    if (process.env.NODE_ENV !== 'production') {
      warn('invalid template option:' + template, this)
    }
    return this
  }
}

这块做的事情很简单,就是拿到templateinnerHTML,调用了idToTemplate来将template传入id时转成template字符串:

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

再看第二个if

if (template) {
 /* istanbul ignore if */
   if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     mark('compile')
   }

   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

   /* istanbul ignore if */
   if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
     mark('compile end')
     measure(`vue ${this._name} compile`, 'compile', 'compile end')
   }
 }

这块的主要部分就是通过compileToFunctionstemplate转成两个函数:renderstaticRenderFns。具体编译部分暂时跳过。
因此总结下就是扩展的这段代码就是为了得到render函数,而不带编译器的vue由于有vue-loader提前执行了编译,所以不需要执行上述代码。

2、公共 $mount 方法

从上述代码可以看到最终扩展的代码也还是执行了之前保存的 mount 方法。我们来看看mount方法的真面目。
entry-runtime-with-compiler.js可以看到Vue的导入来源:
在这里插入图片描述
打开文件找到如下代码:
在这里插入图片描述
由于这块是运行时执行的,因此关于前文对el的判断这里又做了一次。最后调用了mountComponent,可见重点是这个函数,在core/instance/lifecycle.js找到它:
在这里插入图片描述
首先将el保存在vm.$el,接着判断是否存在render,不存在则尝试获取。if判断是用来查看是否在不带编译器的vue中传入了el或者template,抛出一个警告。接着调用beforeMount这个生命周期钩子,并根据不同环境生成不同的updateComponent用来更新组件。
在这里插入图片描述
再往下看,我们发现它调用了Watcher,这是因为更新组件不仅发生在初始渲染,还发生在数据响应式变化后。此时的Watcher被称作渲染Watcher(render watcher)
在这里插入图片描述
最后调用mounted这个生命周期钩子,并将vm实例返回。因此主要逻辑在Watcher中:
在这里插入图片描述
对比Watcher的实例化,我们可以看到传入构造函数的分别是:vm实例updateComponentnoop(一个空函数)一个对象,内部有一个before方法调用beforeUpdate钩子true(表明这是个渲染watcher)。首先保存实例,然后在实例上挂载一个_watcher属性保存当前watcher实例。跳过一段初始化代码,直接看主要部分:
在这里插入图片描述
这段代码主要是给getter赋值为updateComponent,用于后续更新组件。然后调用this.get()触发更新:

get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    value = this.getter.call(vm, vm)
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "${this.expression}"`)
    } else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}

这段就是为了调用updateComponent,而updateComponent内部调用了_update进行更新:
在这里插入图片描述
以上就是个人总结的$mount的执行流程,由于许多部分尚未学习,跳过了很多,总结下就是:

  1. 判断el,调用mountComponent
  2. 获取render函数,调用beforeMount钩子函数
  3. 初始化updateComponent
  4. 实例化一个渲染Watcher,在里面监听变化,调用updateComponent更新视图
  5. 调用mounted钩子函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值