欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

每天学点Vue源码之vm.$mount挂载函数

程序员文章站 2022-04-18 19:04:52
在vue实例中,通过$mount()实现实例的挂载,下面来分析一下$mount()函数都实现了什么功能。 $mount函数执行位置 _init这个私有方法是在执行i...

在vue实例中,通过$mount()实现实例的挂载,下面来分析一下$mount()函数都实现了什么功能。

$mount函数执行位置

每天学点Vue源码之vm.$mount挂载函数

_init这个私有方法是在执行initmixin时候绑定到vue原型上的。

每天学点Vue源码之vm.$mount挂载函数 

$mount函数是如如何把组件挂在到指定元素

$mount函数定义位置

$mount函数定义位置有两个:

第一个是在src/platforms/web/runtime/index.js

每天学点Vue源码之vm.$mount挂载函数

这里的$mount是一个public mount method。之所以这么说是因为vue有很多构建版本, 有些版本会依赖此方法进行有些功能定制, 后续会解释。

// public mount method
// el: 可以是一个字符串或者dom元素
// hydrating 是virtual dom 的补丁算法参数
vue.prototype.$mount = function (
 el?: string | element,
 hydrating?: boolean
): component {
 // 判断el, 以及宿主环境, 然后通过工具函数query重写el。
 el = el && inbrowser ? query(el) : undefined
 // 执行真正的挂载并返回
 return mountcomponent(this, el, hydrating)
}

src/platforms/web/runtime/index.js 文件是运行时版 vue 的入口文件,所以这个方法是运行时版本vue执行的$mount。

关于vue不同构建版本可以看 vue对不同构建版本的解释

关于这个作者封装的工具函数query也可以学习下:

/**
 * query an element selector if it's not an element already.
 */
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
 }
}

第二个定义 $mount 函数的地方是src/platforms/web/entry-runtime-with-compiler.js 文件,这个文件是完整版vue(运行时+编译器)的入口文件。

关于运行时与编译器不清楚的童鞋可以看官网 。

// 缓存运行时候定义的公共$mount方法
const mount = vue.prototype.$mount
vue.prototype.$mount = function (
 el?: string | element,
 hydrating?: boolean
): component {
 // 通过query方法重写el(挂载点: 组件挂载的占位符)
 el = el && query(el)

 /* istanbul ignore if */
 // 提示不能把body/html作为挂载点, 开发环境下给出错误提示
 // 因为挂载点是会被组件模板自身替换点, 显然body/html不能被替换
 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
 }
 // $options是在new vue(options)时候_init方法内执行.
 // $options可以访问到options的所有属性如data, filter, components, directives等
 const options = this.$options
 // resolve template/el and convert to render function
 
 // 如果包含render函数则执行跳出,直接执行运行时版本的$mount方法
 if (!options.render) {
  // 没有render函数时候优先考虑template属性
  let template = options.template
  if (template) {
   // template存在且template的类型是字符串
   if (typeof template === 'string') {
    if (template.charat(0) === '#') {
     // template是id
     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 的类型是元素节点,则使用该元素的 innerhtml 作为模板
    template = template.innerhtml
   } else {
    // 若 template既不是字符串又不是元素节点,那么在开发环境会提示开发者传递的 template 选项无效
    if (process.env.node_env !== 'production') {
     warn('invalid template option:' + template, this)
    }
    return this
   }
  } else if (el) {
   // 如果template选项不存在,那么使用el元素的outerhtml 作为模板内容
   template = getouterhtml(el)
  }
  // template: 存储着最终用来生成渲染函数的字符串
  if (template) {
   /* istanbul ignore if */
   if (process.env.node_env !== 'production' && config.performance && mark) {
    mark('compile')
   }
   // 获取转换后的render函数与staticrenderfns,并挂在$options上
   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 */
   // 用来统计编译器性能, config是全局配置对象
   if (process.env.node_env !== 'production' && config.performance && mark) {
    mark('compile end')
    measure(`vue ${this._name} compile`, 'compile', 'compile end')
   }
  }
 }
 // 调用之前说的公共mount方法
 // 重写$mount方法是为了添加模板编译的功能
 return mount.call(this, el, hydrating)
}

关于idtotemplate方法: 通过query获取该id获取dom并把该元素的innerhtml 作为模板

const idtotemplate = cached(id => {
 const el = query(id)
 return el && el.innerhtml
})

getouterhtml方法:

/**
 * get outerhtml of elements, taking care
 * of svg elements in ie as well.
 */
function getouterhtml (el: element): string {
 if (el.outerhtml) {
  return el.outerhtml
 } else {
  // fix ie9-11 中 svg 标签元素是没有 innerhtml 和 outerhtml 这两个属性
  const container = document.createelement('div')
  container.appendchild(el.clonenode(true))
  return container.innerhtml
 }
}

关于compiletofunctions函数, 在src/platforms/web/entry-runtime-with-compiler.js中可以看到会挂载到vue上作为一个全局方法。

每天学点Vue源码之vm.$mount挂载函数 

mountcomponent方法: 真正执行绑定组件

mountcomponent函数中是出现在src/core/instance/lifecycle.js。

export function mountcomponent (
 vm: component, // 组件实例vm
 el: ?element, // 挂载点
 hydrating?: boolean
): component {
 // 在组件实例对象上添加$el属性
 // $el的值是组件模板根元素的引用
 vm.$el = el
 if (!vm.$options.render) {
  // 渲染函数不存在, 这时将会创建一个空的vnode对象
  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
    )
   }
  }
 }
 // 触发 beforemount 生命周期钩子
 callhook(vm, 'beforemount')

 // vm._render 函数的作用是调用 vm.$options.render 函数并返回生成的虚拟节点(vnode)。template => render => vnode
 
 // vm._update 函数的作用是把 vm._render 函数生成的虚拟节点渲染成真正的 dom。 vnode => real dom node
 
 let updatecomponent // 把渲染函数生成的虚拟dom渲染成真正的dom
 /* 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
 // 创建一个render函数的观察者, 关于watcher后续再讲述.
 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
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。