每天学点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.fn自定义jQuery翻页插件
Jan 20 Javascript
jquery实现简单的表单验证
Nov 17 Javascript
基于vue实现swipe分页组件实例
May 25 Javascript
JavaScript简介_动力节点Java学院整理
Jun 26 Javascript
详解angularJS+Ionic移动端图片上传的解决办法
Sep 13 Javascript
vue2.0 element-ui中el-select选择器无法显示选中的内容(解决方法)
Aug 24 Javascript
原生JS实现的放大镜特效示例【测试可用】
Dec 08 Javascript
vue里如何主动销毁keep-alive缓存的组件
Mar 21 Javascript
如何在Node和浏览器控制台中打印彩色文字
Jan 09 Javascript
js 获取本周、上周、本月、上月、本季度、上季度的开始结束日期
Feb 01 Javascript
es6中class类静态方法,静态属性,实例属性,实例方法的理解与应用分析
Feb 15 Javascript
Vue 按照创建时间和当前时间显示操作(刚刚,几小时前,几天前)
Sep 10 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缩小png图片不损失透明色的解决方法
2013/12/25 PHP
php数组键名技巧小结
2015/02/17 PHP
使用PHPExcel实现数据批量导出为excel表格的方法(必看)
2017/06/09 PHP
客户端静态页面玩分页
2006/06/26 Javascript
用roll.js实现的图片自动滚动+鼠标触动的特效
2007/03/18 Javascript
JQuery 风格的HTML文本转义
2009/07/01 Javascript
高性能web开发 如何加载JS,JS应该放在什么位置?
2010/05/14 Javascript
点击按钮或链接不跳转只刷新页面的脚本整理
2013/10/22 Javascript
jQuery 中DOM 操作详解
2015/01/13 Javascript
JavaScript中的pow()方法使用详解
2015/06/15 Javascript
javaScript数组迭代方法详解
2016/04/14 Javascript
jquery 遍历数组 each 方法详解
2016/05/25 Javascript
JavaScript必知必会(九)function 说起 闭包问题
2016/06/08 Javascript
bootstrap table 表格中增加下拉菜单末行出现滚动条的快速解决方法
2017/01/05 Javascript
ES6中Array.includes()函数的用法
2017/09/20 Javascript
JS实现小球的弹性碰撞效果
2017/11/11 Javascript
jquery获取transform里的值实现方法
2017/12/12 jQuery
JS运算符简单用法示例
2020/01/19 Javascript
vue使用screenfull插件实现全屏功能
2020/09/17 Javascript
小程序实现录音功能
2020/09/22 Javascript
使用Python多线程爬虫爬取电影天堂资源
2016/09/23 Python
破解安装Pycharm的方法
2018/10/19 Python
在cmder下安装ipython以及环境的搭建
2018/10/19 Python
python中import与from方法总结(推荐)
2019/03/21 Python
Python 使用双重循环打印图形菱形操作
2020/08/09 Python
python利用paramiko实现交换机巡检的示例
2020/09/22 Python
实例教程 纯CSS3打造非常炫的加载动画效果
2014/11/05 HTML / CSS
俄罗斯在线水暖商店:Perfecto.ru
2019/10/25 全球购物
Servlet面试题库
2015/07/18 面试题
大学生四个方面的自我评价
2013/09/19 职场文书
员工年终演讲稿
2014/01/03 职场文书
司马光教学反思
2014/02/01 职场文书
竞选学生会主席演讲稿
2014/04/24 职场文书
2015年教研室工作总结范文
2015/05/23 职场文书
严以用权学习心得体会
2016/01/12 职场文书
SpringBoot整合MongoDB的实现步骤
2021/06/23 MongoDB