【Vue源码】第四节数据驱动之$mount方法

_init的最后一步中是调用了$mount

$mountsrc/platform/web/entry-runtime-with-compiler.jssrc/platform/weex/runtime/index.js都有定义。因为$mount方法的实现是和平台、构建方式都相关的。

完整版(Runtime + Compiler)的Vue中,$mount函数在src/platform/web/entry-runtime-with-compiler.js中被重写:

// src/platforms/web/entry-runtime-with-compiler.js
// 备注: 这里先拿到了`Runtime Only`版本的`$mount`方法,然后进行重写
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  // 备注: 如果el是一个字符串,就调用querySelector获取节点并返回,如果节点不存在就抛出警告并创建一个div节点
  //       如果是一个节点就直接返回
  el = el && query(el)

  // ...

  // 最后又调用了`Runtime Only`版本的`$mount`方法  
  return mount.call(this, el, hydrating);
}

参数的类型检查表明el可以是字符串或DOM节点。

// 这里去检查el是不是根节点(html)、(body),如果是就抛出警告并停止挂载。
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
// 备注: 判断render函数是否存在,不存在就要处理一下
if (!options.render) {
  let template = options.template
  // 备注:  判断有无template
  if (template) {
  	// 备注:如果有template的处理
    // 备注: 如果是字符串而且是id选择器,通过idToTemplate方法拿到相应节点,如果拿不到会抛出警告。
    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
          )
        }
      }
    // 备注: 如果template是一个节点,那么获取它的innerHTML
    } else if (template.nodeType) {
      template = template.innerHTML
    // 备注: 如果template既不是字符串也不是一个节点,那么抛出警告并结束挂载。
    } else {
      if (process.env.NODE_ENV !== 'production') {
        warn('invalid template option:' + template, this)
      }
      return this
    }
 
  } else if (el) {
    // 备注: 如果template不存在,接着判断el是否存在,存在则执行template = getOuterHTML(el)
  	// getOuterHTML中先判断el.outerHTML是否存在,有就返回outerHTML
    // 如果没有(ie9-11中 svg标签元素是没有innerHTML 和 outerHTML 这两个属性的
    // 对以上情况的兼容处理: 在el的外面包装了一层div,然后获取该div的innerHTML
    template = getOuterHTML(el)
  }
  // 备注: 到这里无论是template还是el都被转为了字符串模板
    
  // ================================================
  // 备注: template可能是空字符串,要做一下判断
  if (template) {
      // 性能追踪开始
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      // 调用compileToFunctions返回render函数,并挂载到options.render上
      // 在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法,
	  // 无论我们是用单文件 .vue 方式开发组件,还是写了 el 或者 template 属性,最终都会转换成 render 方法,
      // 这个过程是 Vue 的一个“在线编译”的过程
      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

      // 性能追踪结束
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
    // 调用之前缓存的在src/platforms/web/runtime/index.js中定义的$mount函数:
    return mount.call(this, el, hydrating)
}
// src/platforms/web/runtime/index.js
// 这里的 $mount 又把 el 从字符串转换成了节点然后传给了 mountComponent 函数
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
// src/core/instance/lifecycle.js
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // 先将el保存到vm.$el上
  vm.$el = el
  // 然后判断前面的template是否被正确的转换成了render函数
  if (!vm.$options.render) {
    // 如果转换失败,将createEmptyVNode作为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函数是生命周期相关
  callHook(vm, 'beforeMount')

  	let updateComponent
    // 性能追踪
    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 = () => {
        // _render: 调用 vm.$options.render 函数并返回生成的虚拟节点(VNode)
        // _update: 将 VNode 渲染成真实DOM
        vm._update(vm._render(), hydrating)
      }
    }
    // 这里创建了一个 Watcher 实例,显然是与响应式数据相关的
    vm._watcher = new Watcher(vm, updateComponent, noop)
    hydrating = false
    // 函数最后判断为根节点的时候设置 vm._isMounted 为 true, 表示这个实例已经挂载了,
    // 同时执行 mounted 钩子函数。
     if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
      }
      return vm
}

总结

在上一节中,我们通过_init函数,合并了配置项、初始化生命周期、初始化事件中心、初始化渲染、初始化 data、props、computed、watcher 之后,调用了$mount这个函数。
$mount这个函数在多个文件中都有定义,本次分析主要从src/platforms/web/entry-runtime-with-compiler.js这个文件入手,它先是保存了原先在vue上的$mount函数,然后进行改造:

  • 是否能找到这个节点;
  • 判断是否是htmlbody节点;
  • 判断有render无函数,如果没有,判断有无template,总之,这一步无论是template还是el都被转为了字符串模板,之后调用调用compileToFunctions返回render函数,并挂载到options.render上;
  • 判断字符串目标是否是空的,如果不是就调用原先在vue上的$mount函数;
  • 原先在vue上的$mount函数又把 el 从字符串转换成了节点然后传给了 mountComponent 函数;
  • mountComponent会判断有无render函数,有的话就调用生命周期函数beforeMount
  • 调用updateComponent;
  • 创建Watch(初始化的时候会执行回调函数;另一个是当 vm 实例中监测的数据发生变化的时候执行回调函数);
  • 最后判断为根节点的时候设置 vm._isMountedtrue, 表示这个实例已经挂载了,同时执行 mounted 钩子函数。

接下来我们要学习一下_render函数和_update函数

updateComponent = () => {
    // _render: 调用 vm.$options.render 函数并返回生成的虚拟节点(VNode)
    // _update: 将 VNode 渲染成真实DOM
    vm._update(vm._render(), hydrating)
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值