$mount 解析

从runtime with compiler版本的入口开始

src/platforms/web/entry-runtime-with-compiler.js

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
	el?: string | Element,
	hydrating?: boolean
) {
	...
	return mount.call(this, el, hydrating)
}

我们先来看这部分代码,首先将Vue原型上的$mount方法缓存到了一个变量mount上,接着重新定义原型上的$mount方法,最后返回原先方法的调用结果:return mount.call(this, el, hydrating)

这么做是为了复用$mount方法,因为我们在runtime only版本中是没有用到这部分代码的,接下来我们看看这部分代码做了些什么。

el = el && query(el)

这里先是把传入的el转换成DOM对象,elvm.$options.el,它可能是一个字符串,或者是一个DOM对象,那我们具体看看query方法的实现。

export function query (el: string | Element): Element {
  if (typeof el === 'string') {
    const selected = document.querySelector(el)
    if (!selected) {
      process.env.NODE_ENV !== 'production' && warn(
        'Cannot find element: ' + el
      )
      return document.createElement('div')
    }
    return selected
  } else {
    return el
  }
}

这个方法很简单,如果el是字符串,则使用js api获取DOM节点,如果找不到节点则创建一个空的div节点返回,如果el本身就是DOM节点,则直接返回。

我们再回到$mount的代码中:

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')
      }
    }
  }

我们来分析下这部分代码,首先判断options中是否存在render函数,如果没有则执行这部分代码,主要是把template编译成render函数,以便后续渲染。

let template = options.template
if (template) {
	if (typeof template === 'string') {
		if (template.charAt(0) === '#') {
			template = idToTemplate(template)
			...
		}
	}
}

首先是判断有没有传template参数,存在的话判断template的类型,如果是以#开头的值则调用idToTemplate(template)返回结果赋值给template

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

idToTemplate方法其实就是调用了前面说的query方法,通过ID找DOM节点,然后返回其innerHTML

如果没有传template,则去看el,通过getOuterHTML(el)返回innerHTML

function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

getOuterHTML返回el节点的outerHTML,如果没有outerHTML的话则在el外面包一个div再返回它的outerHTML

template最后就是一个HTML字符串,然后将其编译成render函数:

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

最终这里会call原先的$mount方法,也就是在开头缓存的$mount方法,我们看看接下来$mount做了些什么。

再来到 runtime 的入口

上面讲到,runtime with compiler 版本入口把$mount重新定义,然后再执行原先的$mount,那么这个原先的方法又做了些什么呢?

src/platforms/web/runtime/index.js

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

核心代码就是return mountComponent(this, el, hydrating)

mountComponent 解析

src/core/instance/lifecycle

if (!vm.$options.render) {
	vm.$options.render = createEmptyVNode
	...
}

首先是判断是否定义了render,如果没有的话就使用createEmptyVNode来创建一个空的Vnode,关于Vnode后续会详细分析,这里暂时不展开讲。

let updateComponent
updateComponent = () => {
	vm._update(vm._render(), hydrating)
}

然后定义了updateComponent函数,通过调用实例上的_update方法来更新渲染,传入的两个参数,第一个就是前面一系列操作的render函数,第二个参数与服务器渲染相关,这里不讲。

而这个updateComponent函数在什么时候调用呢?

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
}

这里实例化了一个渲染Watcher,关于Watcher,后续也会展开讲,这里大概了解一下它是通过观察者模式调用了updateComponent函数,从而完成实例的挂载。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值