每天学点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 相关文章推荐
javascript 写类方式之四
Jul 05 Javascript
探讨jQuery的ajax使用场景(c#)
Dec 03 Javascript
使用insertAfter()方法在现有元素后添加一个新元素
May 28 Javascript
angularjs基础教程
Dec 25 Javascript
js实现动画特效的文字链接鼠标悬停提示的方法
Mar 02 Javascript
jQuery ajax分页插件实例代码
Jan 27 Javascript
Bootstrap树形控件使用方法详解
Jan 27 Javascript
javascript中异常处理案例(推荐)
Oct 03 Javascript
fckeditor部署到weblogic出现xml无法读取及样式不能显示问题的解决方法
Mar 24 Javascript
vue.js中父组件调用子组件的内部方法示例
Oct 22 Javascript
vue中使用ueditor富文本编辑器
Feb 08 Javascript
jQuery Migrate 插件用法实例详解
May 22 jQuery
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 字符串编码截取函数(兼容utf-8和gb2312)
2009/05/02 PHP
模板引擎正则表达式调试小技巧
2011/07/20 PHP
PHP的password_hash()使用实例
2014/03/17 PHP
PHP常量使用的几个需要注意的地方(谨慎使用PHP中的常量)
2014/09/12 PHP
浅析php静态方法与非静态方法的用法区别
2016/05/17 PHP
PHPStorm2020.1永久激活及下载更新至2020(推荐)
2020/09/25 PHP
关于B/S判断浏览器断开的问题讨论
2008/10/29 Javascript
js一组验证函数
2008/12/20 Javascript
用jquery ajax获取网站Alexa排名的代码
2009/12/12 Javascript
动态加载dtree.js树treeview(示例代码)
2013/12/17 Javascript
js中函数调用的两种常用方法使用介绍
2014/07/17 Javascript
Javascript的表单与验证-非空验证
2016/03/18 Javascript
基于Datatables跳转到指定页的简单实例
2017/11/09 Javascript
纯js实现隔行变色效果
2017/11/29 Javascript
微信小程序异步API为Promise简化异步编程的操作方法
2018/08/14 Javascript
nodejs使用Sequelize框架操作数据库的实现
2020/10/21 NodeJs
Python开发中爬虫使用代理proxy抓取网页的方法示例
2017/09/26 Python
Python实现的求解最大公约数算法示例
2018/05/03 Python
Python 实现网页自动截图的示例讲解
2018/05/17 Python
基于Django与ajax之间的json传输方法
2018/05/29 Python
Python应用库大全总结
2018/05/30 Python
Python read函数按字节(字符)读取文件的实现
2019/07/03 Python
tensorflow查看ckpt各节点名称实例
2020/01/21 Python
详解如何通过H5(浏览器/WebView/其他)唤起本地app
2017/12/11 HTML / CSS
phpquery中文手册
2021/03/18 PHP
Clarks英国官方网站:全球领军鞋履品牌
2016/11/26 全球购物
创建索引时需要注意的事项
2013/05/13 面试题
简单的大学生自我鉴定
2014/02/18 职场文书
《假如》教学反思
2014/04/17 职场文书
十佳护士先进事迹
2014/05/08 职场文书
高三毕业典礼演讲稿
2014/05/13 职场文书
先进基层党组织事迹材料
2014/12/25 职场文书
幼儿园母亲节活动总结
2015/02/10 职场文书
政协常委会议主持词
2015/07/03 职场文书
教学工作总结范文5篇
2019/08/19 职场文书
Oracle数据库中通用的函数实例详解
2022/03/25 Oracle