每天学点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 相关文章推荐
漂亮的仿flash菜单,来自蓝色经典
Jun 26 Javascript
TreeView 用法(有代码)(asp.net)
Jul 15 Javascript
jQuery回车实现登录简单实现
Aug 20 Javascript
JavaScript实现将数组中所有元素连接成一个字符串的方法
Apr 06 Javascript
使用控制台破解百小度一个月只准改一次名字
Aug 13 Javascript
JS实现仿Windows经典风格的选项卡Tab切换代码
Oct 20 Javascript
Angularjs 创建可复用组件实例代码
Oct 09 Javascript
Angularjs中使用轮播图指令swiper
May 30 Javascript
vue之数据交互实例代码
Jun 16 Javascript
vue-router单页面路由
Jun 17 Javascript
基于vue-cli创建的项目的目录结构及说明介绍
Nov 23 Javascript
vue中el-input绑定键盘按键(按键修饰符)
Jul 22 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
phpwind中的数据库操作类
2007/01/02 PHP
URL Rewrite的设置方法
2007/01/02 PHP
PHP令牌 Token改进版
2008/07/18 PHP
PHP统计目录中文件以及目录中目录大小的方法
2016/01/09 PHP
Javascript YUI 读码日记之 YAHOO.util.Dom - Part.3
2008/03/22 Javascript
NodeJs中的非阻塞方法介绍
2012/06/05 NodeJs
JS获取并操作iframe中元素的方法
2013/03/21 Javascript
jQery使网页在显示器上居中显示适用于任何分辨率
2014/06/09 Javascript
jQuery中click事件的定义和用法
2014/12/20 Javascript
浅谈jQuery animate easing的具体使用方法(推荐)
2016/06/17 Javascript
JS使用cookie设置样式的方法
2016/06/30 Javascript
angular基于路由控制ui-router实现系统权限控制
2016/09/27 Javascript
Nodejs下DNS缓存问题浅析
2016/11/16 NodeJs
正则验证小数点后面只能有两位数的方法
2017/02/28 Javascript
Javascript 实现匿名递归的实例代码
2017/05/25 Javascript
Require.js的基本用法详解
2017/07/03 Javascript
linux 后台运行node服务指令方法
2018/05/23 Javascript
详解vue项目中如何引入全局sass/less变量、function、mixin
2018/06/02 Javascript
vue脚手架搭建项目的兼容性配置详解
2018/07/17 Javascript
浅谈vue同一页面中拥有两个表单时,的验证问题
2018/09/18 Javascript
[27:02]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS LGD第三场
2014/05/24 DOTA
mac使用python识别图形验证码功能
2020/01/10 Python
python代码如何实现余弦相似性计算
2020/02/09 Python
python3判断IP地址的方法
2021/03/04 Python
HTML5各种头部meta标签的功能(推荐)
2017/03/13 HTML / CSS
比较基础的php面试题及答案-编程题
2012/10/14 面试题
毕业生的自我评价
2013/12/30 职场文书
《挑山工》的教学反思
2014/02/16 职场文书
商超业务员岗位职责
2014/03/12 职场文书
服务理念标语
2014/06/18 职场文书
工伤事故证明
2014/10/20 职场文书
中班下学期幼儿评语
2014/12/30 职场文书
教师个人发展总结
2015/02/11 职场文书
小学生五一劳动节演讲稿
2015/03/18 职场文书
东京审判观后感
2015/06/01 职场文书
党员干部学习三严三实心得体会
2016/01/05 职场文书