上一篇中,咱们一起探讨了new Vue({...})背地产生了什么。那么当咱们实例化vue之后,进行dom挂载又产生了什么呢?

仔细的同学会发现:$mount办法在多个文件中被定义,如:

  • src/platform/web/entry-runtime-with-compiler.js
  • src/platform/web/runtime/index.js
  • src/platform/weex/runtime/index.js

之所以有多个中央,是因为$mount实现是和平台、构建形式都相干的
上面,咱们抉择compiler版本剖析

一. $mount 骨干代码如下:

Vue.prototype.$mount = function(el?: string | Element, hydrating?: boolean): Component {  el = el && query(el)  // query办法,实际上是对el参数做了一个转化,el可能是string 或者 element。如果是string,将返回document.querySelector(el)   // ...  const options = this.$options  if (!options.render) {    // render函数不存在    let template = options.template        if (template) {       // 如果存在template配置项:       // 1. template可能是"#xx",那么依据id获取element内容       // 2. 如果template存在nodeType,那么获取template.innerHTML 内容    }else {       // 如果template配置项不存在template,然而存在el:      /*         * 例如: new Vue({        *       el: "#app",      *       ...       *  })       *       */      // 那么依据el获取对应的element内容    }    // 通过下面的解决,将获取的template做为参数调用compileToFunctions办法    // compileToFunctions办法会返回render函数办法,render办法会保留到vm.$options上面    const { render, staticRenderFns } = compileToFunctions(template, {...})    options.render = render  }  return mount.call(this, el, hydrating)}

从骨干代码咱们能够看出做了以下几件事

  • 因为el参数有两种类型,可能是string 或者 element,调用query办法,对立转化为Element类型
  • 如果没有手写render函数, 那么先获取template内容。再将template做为参数,调用compileToFunctions办法,返回render函数。
  • 最初调用mount.call,这个办法实际上会调用runtime/index.js的mount办法

注:

  1. vue compiler别离2个版本:一个是构建时版本,即咱们应用vue-loader + webpack。另一个版本是:运行时版本,运行的时候,再去compiler解析。咱们这里剖析的是 运行时
  2. vue最终只认render函数,所以如果咱们手动写render函数,那么就间接调用mount.call。反之,vue会将template做为参数,运行时调用compileToFunctions办法,转化为render函数,再去调用mount.call办法。
  3. 如果是构建时版本,vue-loader + webpack,会先将咱们本地的代码转化成render函数,运行将间接调用mount.call。生产环境,咱们举荐构建时的版本。集体学习举荐运行时版本。
  4. mount.call办法,实际上会调用runtime/index.js上面的$mount办法,而这个办法很简略,将会调用mountComponent办法。

二. mountComponent 骨干代码如下:

export function mountComponent(vm: Component, el: ?Element, hydrating?: boolean): Component {  // ...  // 调用beforeMount生命周期函数  callHook(vm, 'beforeMount')  // ...  // 定义updateComonent办法  let updateComponent = () => {      vm._update(vm._render(), hydrating)  }    // ...  new Watcher(vm, updateComponent, noop, {    before () {      if (vm._isMounted && !vm._isDestroyed) {        callHook(vm, 'beforeUpdate')      }    }  }, true)  // ...  // 调用生命周期函数mounted  callHook(vm, 'mounted')}

Watch类相干代码

Watch类有许多逻辑,这里咱们只看和$mount相干的:

class Watcher {    constructor(        vm: Component,        expOrFn: string | Function,        cb: Function,        options?: ?Object,        isRenderWatcher?: boolean    ){        // ...        if (typeof expOrFn === 'function') {          this.getter = expOrFn        }else {          // ...        }        // ...        this.value = this.lazy ? undefined : this.get()    }    get() {      // ...      value = this.getter.call(vm, vm)      // ...      // cleanupDeps办法前面咱们会剖析,这个在性能优化上比拟重要      return value;    }}

从下面代码,能够看出:

  • 先调用beforeMount钩子函数
  • 将updateComponent办法做为参数,实例化Watch。Watch在这个有2个作用:
    1、初始化的时候会执行回调函数
    2、当 vm 实例中的监测的数据发生变化的时候执行回调函数
    这里,咱们先看第1个。 第2个将在数据变动监测章节剖析
    执行回调后,咱们看到vm._update(vm._render(), hydrating)办法,这个办法分2个步骤:
    (1) 执行render办法,返回最新的 VNode节点树
    (2) 调用update办法,实际上进行diff算法比拟,实现一次渲染
  • 调用mounted钩子函数

三. 总结

  1. options上无render函数,对template, el做解决,获取template内容。
  2. 调用compileToFunctions办法,获取render函数,增加到options.render上
  3. 调用mount.call,实际上是调用mountComponent函数
  4. 调用beforeMount钩子
  5. 实例化渲染watcher,执行回调
  6. 依据render函数获取VNode节点树 (其实是一个js对象)
  7. 执行update办法,实际上是patch过程,vue会执行diff算法,实现一次渲染
  8. 调用mounted钩子

在上面的章节,咱们将陆续剖析: 响应式,compileToFunctions, 虚构DOM,以及patch
码字不易,多多关注~????