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


Posted in Javascript onMarch 11, 2019

在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(运行时+编译器)的入口文件。

关于运行时与编译器不清楚的童鞋可以看官网 运行时 + 编译器 vs. 只包含运行时 。

// 缓存运行时候定义的公共$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
}

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

Javascript 相关文章推荐
jquery each()源代码
Feb 14 Javascript
JS判断不能为空实例代码
Nov 26 Javascript
js 金额格式化来回转换示例
Feb 23 Javascript
js实现简单div拖拽功能实例
May 12 Javascript
javascript 中的 delete及delete运算符
Nov 15 Javascript
第一次动手实现bootstrap table分页效果
Sep 22 Javascript
利用node.js制作命令行工具方法教程(一)
Jun 22 Javascript
基于 Vue.js 之 iView UI 框架非工程化实践记录(推荐)
Nov 21 Javascript
手挽手带你学React之React-router4.x的使用
Feb 14 Javascript
vuecli项目构建SSR服务端渲染的实现
Oct 30 Javascript
微信小程序弹窗禁止页面滚动的实现代码
Dec 30 Javascript
一文帮你理解PReact10.5.13源码
Apr 03 Javascript
JavaScript中常用的简洁高级技巧总结
Mar 10 #Javascript
angular 实现下拉列表组件的示例代码
Mar 09 #Javascript
Node.js动手撸一个静态资源服务器的方法
Mar 09 #Javascript
深入理解使用Vue实现Context-Menu的思考与总结
Mar 09 #Javascript
vue模块拖拽实现示例代码
Mar 09 #Javascript
Vue中的验证登录状态的实现方法
Mar 09 #Javascript
在NPM发布自己造的轮子的方法步骤
Mar 09 #Javascript
You might like
在PHP的图形函数中显示汉字
2006/10/09 PHP
解决中英文字符串长度问题函数
2007/01/16 PHP
PHP和Mysqlweb应用开发核心技术 第1部分 Php基础-1 开始了解php
2011/07/03 PHP
php+html5基于websocket实现聊天室的方法
2015/07/17 PHP
php+ajax制作无刷新留言板
2015/10/27 PHP
PHP正则匹配到2个字符串之间的内容方法
2018/12/24 PHP
jQuery 使用手册(四)
2009/09/23 Javascript
ie8本地图片上传预览示例代码
2014/01/12 Javascript
jquery如何扑捉回车键触发的事件
2014/04/24 Javascript
jQuery圆形统计图开发实例
2015/01/04 Javascript
WebApi+Bootstrap+KnockoutJs打造单页面程序
2016/05/16 Javascript
Node.js 使用命令行工具检查更新
2017/06/08 Javascript
javascript设计模式 ? 适配器模式原理与应用实例分析
2020/04/13 Javascript
JavaScript语句错误throw、try及catch实例解析
2020/08/18 Javascript
javascript使用canvas实现饼状图效果
2020/09/08 Javascript
[26:40]DOTA2上海特级锦标赛A组资格赛#1 Secret VS MVP.Phx第一局
2016/02/25 DOTA
Python中的pass语句使用方法讲解
2015/05/14 Python
在Python中操作字符串之startswith()方法的使用
2015/05/20 Python
Python 多核并行计算的示例代码
2017/11/07 Python
Python matplotlib画图实例之绘制拥有彩条的图表
2017/12/28 Python
python解析html提取数据,并生成word文档实例解析
2018/01/22 Python
对PyQt5基本窗口控件 QMainWindow的使用详解
2019/06/19 Python
python nmap实现端口扫描器教程
2020/05/28 Python
Python代理IP爬虫的新手使用教程
2019/09/05 Python
一行Python代码制作动态二维码的实现
2019/09/09 Python
关于ResNeXt网络的pytorch实现
2020/01/14 Python
快速解决Django关闭Debug模式无法加载media图片与static静态文件
2020/04/07 Python
香港草莓网土耳其网站:Strawberrynet TR
2017/03/02 全球购物
市场营销方案范文
2014/03/11 职场文书
好学生评语大全
2014/05/05 职场文书
党员反对四风思想汇报范文
2014/10/25 职场文书
捐款感谢信
2015/01/20 职场文书
红高粱观后感
2015/06/10 职场文书
2015年学校远程教育工作总结
2015/07/20 职场文书
一文搞懂如何实现Go 超时控制
2021/03/30 Python
JMeter对MySQL数据库进行压力测试的实现步骤
2022/01/22 MySQL