关于html5:vue源码解析mount

7次阅读

共计 2958 个字符,预计需要花费 8 分钟才能阅读完成。

上一篇中,咱们一起探讨了 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
码字不易,多多关注~????

正文完
 0