vm.$mount的实例挂在解析——vue源码分析

实例挂在还有这么多有意思的内容

不错鼓励并赞赏 标签: Javascript      评论 / 2019-09-15

vm.$mount的实例挂在解析——vue源码分析

Hello ! 大家好,今天小编本人基于之前的源码分析后的的后续方法解读。本篇主要分析,Vue的实例挂在的过程。这也是它的核心流程

再解析之前,我们回顾一下知识点:webpack打包和编译的流程(Runtime Only)Runtime and Compiler 具体内容请查看前面的Vue源码章节细看。

这次主要还是从入口文件为模式是Runtime and Compiler的开始分析,入口文件src/platforms/web/entry-runtime-width-compiler.js分析。

代码是 Vue.prototype.$mount

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* 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
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        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(`${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

Code : 1.1

我们一起来分析这段挂在实例的代码,第一行const mount = Vue.prototype.$mount

为啥会临时存储呢?

因为这要从src/platforms/runtime/index.js说起,这个主要是给 Runtime Only 复用做的,因为这个模式下没有上面的业务逻辑。

那我们看一看这个src/platforms/runtime/index.js的$mount方法吧:

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

Code : 1.2

基本上就是判断当前是否是客户端浏览器,然后执行mountComponent(this, el, hydrating)

这个方法代码如下:

function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  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(`${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

Code : 1.3

上来就是vm.$el = el 进行实际节点挂在所用。

紧跟着就是vm.$options.render这个方法不存在则生成一个对应的render方法。

其中createEmptyVNode callHook updateComponent 后续会讲到,这里略过。 总之这里就是没有render就造一个出来。render就是个最后的重要凭证。一切转模版转render

我们只是看看最为重要的vm._watcher = new Watcher(vm, updateComponent, noop) 这个方法

代码路径在src/core/instance/observer/watcher.jsWatcher方法

...
if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
          ? undefined
          : this.get()
...    

Code : 1.4

其中 this.getter对应Watcher中的第二个参数updateComponent。最后this.value赋值过程中主动调用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
  }

Code : 1.5

看到 value = this.getter.call(vm, vm) 会主动调用this.getter,也就是会调用updateComponent的这个方法。

方法在src/core/instance/lifecycle里面执行,执行代码片段是:

vm._update(vm._render(), hydrating)

Code : 1.6

这里面就是把之前好不容易的的各种形式的template#template、手写render等统一转换的render方法与实际的DOM做diff与替换。

本意到这里就完结了,没想到后面还有一行代码

vm._watcher = new Watcher(vm, updateComponent, noop)

Code : 1.7

这句话的意义就是不光这次初始化需要替换,还在后续的数据放生变化的时候进行相应的替换。需要观察模式介入。

好了到这里基本$mount方法分析完毕。

我们再回到Code 1.1 的代码片段

可以看到,后续的Vue.prototype.$mount的重新定义与src/platforms/runtime/index.js中的$mount是一样的。这里就不再解释代码含义了。

总结:

最后$mount的方法意义在于就是将模版生成render函数,然后再去同步即可。包含后续的观察者模式持续的同步数据和Dom的变化。

其中很多细节方法本章莫有具体分析,不影响整体的分析流程。不过后面章节会详细介绍相关的具体方法含义与意义。

Hi 看这里!

大家好,我是PRO

我会陆续分享生活中的点点滴滴,当然不局限于技术。希望笔墨之中产生共鸣,每篇文章下面可以留言互动讨论。Tks bd!

博客分类

您可能感兴趣

作者推荐

呃,突然想说点啥

前端·博客

您的鼓励是我前进的动力---

使用微信扫描二维码完成支付