每天学点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实用基础超详细介绍
Apr 11 Javascript
JS获取表格内指定单元格html内容的方法
Mar 31 Javascript
JavaScript事件类型中焦点、鼠标和滚轮事件详解
Jan 25 Javascript
在JavaScript中模拟类(class)及类的继承关系
May 20 Javascript
jQuery实现文档树效果
Feb 20 Javascript
js实现日历与定时器
Feb 22 Javascript
vue使用watch 观察路由变化,重新获取内容
Mar 08 Javascript
微信小程序实现简单表格
Feb 14 Javascript
微信小程序用户授权,以及判断登录是否过期的方法
May 10 Javascript
Vue中实现权限控制的方法示例
Jun 07 Javascript
开发中常用的25个JavaScript单行代码(小结)
Jun 28 Javascript
VUEX-action可以修改state吗
Nov 19 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 setcookie设置Cookie用法(及设置无效的问题)
2011/07/13 PHP
PHP串行化与反串行化实例分析
2016/12/27 PHP
jquery isEmptyObject判断是否为空对象的函数
2011/02/14 Javascript
javascript中call和apply方法浅谈
2013/09/27 Javascript
JavaScript中函数(Function)的apply与call理解
2015/07/08 Javascript
jquery实现图片上传之前预览的方法
2015/07/11 Javascript
jQuery插件windowScroll实现单屏滚动特效
2015/07/14 Javascript
Validform+layer实现漂亮的表单验证特效
2016/01/17 Javascript
纯js实现悬浮按钮组件
2016/12/17 Javascript
Three.js加载外部模型的教程详解
2017/11/10 Javascript
基于node.js实现微信支付退款功能
2017/12/19 Javascript
Vue二次封装axios为插件使用详解
2018/05/21 Javascript
深入浅析Vue中的Prop
2018/06/10 Javascript
JS实现HTML页面中动态显示当前时间完整示例
2018/07/30 Javascript
vue框架制作购物车小球动画效果实例代码
2019/09/26 Javascript
微信小程序12行js代码自己写个滑块功能(推荐)
2020/07/15 Javascript
Python面向对象之静态属性、类方法与静态方法分析
2018/08/24 Python
Python上下文管理器类和上下文管理器装饰器contextmanager用法实例分析
2019/11/07 Python
python实现按关键字筛选日志文件
2019/12/24 Python
如何学习Python time模块
2020/06/03 Python
如何向scrapy中的spider传递参数的几种方法
2020/11/18 Python
一款利用html5和css3实现的3D滚动特效的教程
2015/01/04 HTML / CSS
香港最新科技与优质家居产品购物网站:J SELECT
2018/08/21 全球购物
Perfume’s Club法国站:购买香水和化妆品
2019/05/02 全球购物
TheFork葡萄牙:欧洲领先的在线餐厅预订平台
2019/05/27 全球购物
新闻记者实习自我鉴定
2013/09/19 职场文书
医院护士求职自荐信格式
2013/09/21 职场文书
资深财务管理人员自我评价
2013/09/22 职场文书
统计岗位职责
2014/02/21 职场文书
志愿者活动总结范文
2014/04/26 职场文书
2015年元旦主持词结束语
2014/12/14 职场文书
颐和园英文导游词
2015/01/30 职场文书
Python的flask接收前台的ajax的post数据和get数据的方法
2021/04/12 Python
Python基于Tkinter开发一个爬取B站直播弹幕的工具
2021/05/06 Python
关于React Native 无法链接模拟器的问题
2021/06/21 Javascript
Redis之RedisTemplate配置方式(序列和反序列化)
2022/03/13 Redis