美文网首页
vew源码分析二 -- $mount做的事情

vew源码分析二 -- $mount做的事情

作者: json_q | 来源:发表于2020-03-22 11:10 被阅读0次

前言

在上一篇中的最后我们讲到了vue的渲染是经过了vm.$mount,现在我们看看$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, {
        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')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

解释

  • 首先这个函数最开始判断了这个el是不是body或者html,如果是的话就会报错,不能这么写。因为vue渲染dom实际是进行替换,这样如果是body的话,那么body就会被替换掉。
  • 第二步进行一个render的判断,如果没有render函数,就根据template编译出一个render函数,如果没有template了?就根据el执行template = getOuterHTML(el),生成一个template。然后根据compileToFunctions编译成一个render函数。
  • 最后执行mount.call(this, el, hydrating),此时的mount是原先挂载在原型上的方法,具体/src/platform/web/runtime/index.js

原型的mount函数

我们来看看之前原型的mount的源码

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

// devtools global hook
/* istanbul ignore next */
if (inBrowser) {
  setTimeout(() => {
    if (config.devtools) {
      if (devtools) {
        devtools.emit('init', Vue)
      } else if (
        process.env.NODE_ENV !== 'production' &&
        process.env.NODE_ENV !== 'test'
      ) {
        console[console.info ? 'info' : 'log'](
          'Download the Vue Devtools extension for a better development experience:\n' +
          'https://github.com/vuejs/vue-devtools'
        )
      }
    }
    if (process.env.NODE_ENV !== 'production' &&
      process.env.NODE_ENV !== 'test' &&
      config.productionTip !== false &&
      typeof console !== 'undefined'
    ) {
      console[console.info ? 'info' : 'log'](
        `You are running Vue in development mode.\n` +
        `Make sure to turn on production mode when deploying for production.\n` +
        `See more tips at https://vuejs.org/guide/deployment.html`
      )
    }
  }, 0)
}

解释

  • 这个方法比较简单就是直接调用了mountComponent,然后进行了一系列的环境判断
  • mountComponent 在文件core/instance/lifecycle里面

mountComponent 做了什么

以下是mountCoponnetde 源码

export 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(`vue ${name} render`, startTag, endTag)

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

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  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 hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

解释

  • 判断有没有render,没有render的话直接报警告
  • callHook(vm, 'beforeMount'); 这一步就是执行注册在组件里面得beforeMount生命周期函数
  • 然后进行了一个config.performance的判断,这个是一个性能埋点,如果开启了performance,性能会好一点,里面具体逻辑不做分析
  • 然后就是比较关键的updateComponent 赋值,仅仅是赋值,并没有执行,那么在哪里执行的了?仅接着后面有一个new Watcher(vm, updateComponent, noop, {}, true);在这个watch里面执行的,这是一个渲染watch,为什么不直接执行updateComponent,因为直接执行的话,指挥执行一次,放在watch里面,可以监听变化继续在有变化的时候继续执行
  • 所以最后执行了vm._update(vm._render(), hydrating),在执行vm._update之前先执行了vm._render(),这函数就是创建一个vmDom的,我们分一篇文章继续讲解或者函数

相关文章

网友评论

      本文标题:vew源码分析二 -- $mount做的事情

      本文链接:https://www.haomeiwen.com/subject/wrumyhtx.html