WuShaolin

悟已往之不谏,知来者之可追!

0%

Vue实例挂载的内在

什么叫数据驱动式

上一篇文章大体了解了Vue项目的起手式,本文深入一下挂载实例的本质。在这之前先了解一下vue的一个开发理念-数据驱动式

传统的jq开发方式是操作DOM,进行视图层的更新,现代主流的web开发通过修改数据的形式来进行视图层的更新,也就是说数据和视图层有了双向的或者单向的关联关系,DOM变成了数据的一种映射关系。

简单的示例

<div id="app> 
  {{ message }} 
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'hello Vue'
  }
})

上面的div的内容将会渲染成hello Vue,而且修改div内容里面vue实例里面message也会变化,这种双向绑定后续文章再讨论😊,本文主要分析上面div的内容怎么变成了hello Vue

那么new Vue干了什么呢

在上一篇文章中我们看了Vue的构造函数

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

当我们通过new关键字创建一个vue实例时,会调用原型上的_init(),这部分代码也在上一篇文章中提到了,这个方法的最后调用了vue原型上的$mount(),这个方法在src/platforms/web/entry-runtime-with-compiler.js中,(platforms的其他平台也有,先以这个版本为例)

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, {
        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)
}

mount方法干了啥

上述代码的第一句const mount = Vue.prototype.$mount,使用一个局部变量mount用来缓存原型上的$mount,接下来又重新定义了该方法。

原型上的$mount方法src/platforms/web/runtime/index.js

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

重新定义的方法中首先对挂载点el的类型做了限制-不能是document.body document.documentElement,也就是不能是body html这样的根节点。

其次,如果没有定义render方法的话,会把el或者template字符串转换成render方法,也就是说,Vue组件最终都会需要render方法。