vue $mount 和 el的区别说明


Posted in Javascript onSeptember 11, 2020

两者在使用效果上没有任何区别,都是为了将实例化后的vue挂载到指定的dom元素中。

如果在实例化vue的时候指定el,则该vue将会渲染在此el对应的dom中,反之,若没有指定el,则vue实例会处于一种“未挂载”的状态,此时可以通过$mount来手动执行挂载。

注:如果$mount没有提供参数,模板将被渲染为文档之外的的元素,并且你必须使用原生DOM API把它插入文档中。

例如:

var MyComponent = Vue.extend({
 template: '<div>Hello!</div>'
})

// 创建并挂载到 #app (会替换 #app)
new MyComponent().$mount('#app')

// 同上
new MyComponent({ el: '#app' })

// 或者,在文档之外渲染并且随后挂载
var component = new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)

补充知识:Vue 实例挂载方法($mount)的实现

在 Vue 的 _init 方法中已经回调了beforeCreate 和created这两个生命周期钩子,在此之后就进行了实例的挂载

if (vm.$options.el) { // 挂载实例
   vm.$mount(vm.$options.el);
  }

在挂载函数中,将要进行 beforeMount 和 mounted 的回调。

在不同的平台下对于 $mount 函数的实现是有差异的,下面考虑 web 平台的 runtime-with-compiler 版本 , 其在web平台下的定义如下(src/platforms/web/runtime/index.js)

import { mountComponent } from 'core/instance/lifecycle';

Vue.prototype.$mount = function(
 el?: string | Element,
 hydrating?: boolean
): Component {
 el = el && inBrowser ? query(el) : undefined;
 
 return mountComponent(this, el, hydrating);
};

在$mount函数的参数中,第一个为我们属性的el, 第二个参数为服务端渲染有关,在patch函数中用到,这里可以忽略。

但是在调用这个$mount函数的时候,首先调用的是不同版本下的$mount函数,然后在该函数中再调用相应平台的$mount函数,如下在 runtime-with-compiler 版本中$mount函数如下(src/platforms/web/entry-runtime-with-compiler.js)

import Vue from './runtime/index';
const mount = Vue.prototype.$mount; // 缓存 上面的 $mount 方法
Vue.prototype.$mount = function(
 el?: string | Element,
 hydrating?: boolean
): Component {
 el = el && query(el);

 // 不能挂载到 body 和 html 上
 if (el === document.body || el === document.documentElement) {   
  return this;
 }

 const options = this.$options;

 if (!options.render) { // 如果没有 render 函数
  // ... 将 render 函数添加到 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;
  // ...
 }
 
 return mount.call(this, el, hydrating);
};

可知该函数主要干了三件事

1、由于挂载之后会替换被挂载的对象,所以限制不能挂载到 body 和 html 上

2、如果当前Vue实例没有 render() 函数(写template等),则通过编译等手段,将render函数添加到 options 上

3、调用在代码开头我们先缓存的$mount方法,该方法就是web平台下的方法。

在web平台下的$mount方法里面主要就是调用了mountComponent() 方法,接下来我们的核心就是该方法了

在'core/instance/lifecycle.js 文件中我们找到了该方法的定义,删掉一些非重点代码后如下

export function mountComponent(
 vm: Component,
 el: ?Element,
 hydrating?: boolean
): Component {
 vm.$el = el;
 if (!vm.$options.render) { 
  // 不是重点,该处主要是用来对没有 render 函数下的一些错误提示
 }
 callHook(vm, 'beforeMount'); // 回调 beforeMount , 开始准备挂载实例

 // 声明 更新组件 的函数 (源代码中有关performance配置不是重点,故省略) 
 const updateComponent = updateComponent = () => {
   vm._update(vm._render(), hydrating);
 };

 // new 一个 Watcher [isRenderWatcher]
 new Watcher(vm, updateComponent, noop, {
  before() {
   if (vm._isMounted && !vm._isDestroyed) {
    callHook(vm, 'beforeUpdate');
   }
  },
 }, true /* isRenderWatcher */);
 hydrating = false;

 // Vue 的根实例的 mounted 回调在这里执行
 if (vm.$vnode == null) {
  vm._isMounted = true;
  callHook(vm, 'mounted');
 }
 
 return vm;
}

上面的代码中主要干了如下三件事

1、回调 beforeMount

2、生成 updateComponent 方法,该方法将 vnode 渲染为真实的DOM

3、new 一个 Watcher ,并在该 Watcher在调用updateComponent方法

4、回调 mounted

对于 updateComponent方法较为复杂,其内部主要调用_update()将 vnode渲染为浏览器上显示的真实DOM

我们考虑如下两个问题

1. Watcher 中如何调用 updateComponent方法

Watcher 函数的构造函数接受如下的参数

constructor(
  vm: Component,
  expOrFn: string | Function,
  cb: Function,
  options?: ?Object,
  isRenderWatcher?: boolean
 )

在上面的代码中,updateComponent()方法作为第二个参数传递过来,即构造函数中的expOrFn

往下看会看到

if (typeof expOrFn === 'function') {
   this.getter = expOrFn;
  }

也就是说updateComponent()方法被设置为了getter()方法

看到构造函数的最后

this.value = this.lazy
   ? undefined
   : this.get();

其中 lazy 属性的值在前面被设置为了 false

this.lazy = !!options.lazy; // 我们 options 中没有 lazy 属性

这也就是说,咋i构造函数的末尾会调用this.get(),而在this.get()中

const vm = this.vm;
  try {
   value = this.getter.call(vm, vm);
  }

我们看到调用了getter()方法,也就是调用了updateComponent()方法。

2. 为什么根实例的$vnode为空

在initRender()函数中有如下代码

const parentVnode = vm.$vnode = options._parentVnode;

也就是说 当前实际的 $vnode 值为其父节点的vnode值

而根实例没有父节点,故其$vnode值就为空了,所以会执行

if (vm.$vnode == null) {
  vm._isMounted = true;
  callHook(vm, 'mounted');
 }

那么子节点的mounted回调是在那里执行的呢?

在 path()(core/vdom/patch.js) 函数中有如下代码

function invokeInsertHook(vnode, queue, initial) {
  if (isTrue(initial) && isDef(vnode.parent)) {
   vnode.parent.data.pendingInsert = queue;
  }
  else {
   for (let i = 0; i < queue.length; ++i) {
    queue[i].data.hook.insert(queue[i]); // 这里
   }
  }
 }

在循环queue的时候,调用了 insert()方法,该方法为 VNodeHooks,其在componentVNodeHooks(core/vdom/create-component.js)中声明,代码如下

const componentVNodeHooks = {
 insert(vnode: MountedComponentVNode) {
  const { context, componentInstance } = vnode;

  if (!componentInstance._isMounted) {
   componentInstance._isMounted = true;
   callHook(componentInstance, 'mounted'); // 这里
  }
  if (vnode.data.keepAlive) {
   if (context._isMounted) {
    queueActivatedComponent(componentInstance);
   }
   else {
    activateChildComponent(componentInstance, true /* direct */);
   }
  }
 },
}

由于 path() 方法在 _update()函数中调用,这部不再重点说明。

下节我们将来说说render() 和 _update() 方法的实现

以上这篇vue $mount 和 el的区别说明就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Ext 今日学习总结
Sep 19 Javascript
JQUERY的属性选择符和自定义选择符使用方法(二)
Apr 07 Javascript
自己实现string的substring方法 人民币小写转大写,数字反转,正则优化
Sep 02 Javascript
分享JavaScript获取网页关闭与取消关闭的事件
Dec 13 Javascript
jquery中使用循环下拉菜单示例代码
Sep 24 Javascript
JS制作简单的三级联动
Mar 18 Javascript
jQuery定义背景动态切换效果的方法
Mar 23 Javascript
js中for in语句的用法讲解
Apr 24 Javascript
JavaScript各类型的关系图解
Oct 16 Javascript
jquery实现的伪分页效果代码
Oct 29 Javascript
JavaScript统计网站访问次数的实现代码
Nov 18 Javascript
前端图片懒加载(lazyload)的实现方法(提高用户体验)
Aug 21 Javascript
JavaScript 判断数据类型的4种方法
Sep 11 #Javascript
jQuery实现日历效果
Sep 11 #jQuery
JS实现密码框效果
Sep 10 #Javascript
JavaScript常用工具函数库汇总
Sep 17 #Javascript
el-form 多层级表单的实现示例
Sep 10 #Javascript
详解React的回调渲染模式
Sep 10 #Javascript
vue-cli3项目配置eslint代码规范的完整步骤
Sep 10 #Javascript
You might like
PHP分页显示制作详细讲解
2006/10/09 PHP
php 取得瑞年与平年的天数的代码
2009/08/10 PHP
php通过排列组合实现1到9数字相加都等于20的方法
2015/08/03 PHP
thinkPHP框架实现类似java过滤器的简单方法示例
2018/09/05 PHP
xss文件页面内容读取(解决)
2010/11/28 Javascript
js用Date对象处理时间实现思路及代码
2013/01/31 Javascript
jquery实现在页面加载完毕后获取图片高度或宽度
2014/06/16 Javascript
javascript数组常用方法汇总
2016/09/10 Javascript
有关文件上传 非ajax提交 得到后台数据问题
2016/10/12 Javascript
js实现音频控制进度条功能
2017/04/01 Javascript
node.js操作mysql简单实例
2017/05/25 Javascript
js+html5实现半透明遮罩层弹框效果
2020/08/24 Javascript
Vue + better-scroll 实现移动端字母索引导航功能
2018/05/07 Javascript
layui的table中显示图片方法
2018/08/17 Javascript
小程序获取周围IBeacon设备的方法
2018/10/31 Javascript
nodejs中各种加密算法的实现详解
2019/07/11 NodeJs
微信小程序开发搜索功能实现(前端+后端+数据库)
2020/03/04 Javascript
JS字符串补全方法padStart()和padEnd()
2020/05/27 Javascript
vue中实现弹出层动画效果的示例代码
2020/09/25 Javascript
[01:01:25]DOTA2上海特级锦标赛B组资格赛#2 Fnatic VS Spirit第三局
2016/02/27 DOTA
极简的Python入门指引
2015/04/01 Python
Python3使用turtle绘制超立方体图形示例
2018/06/19 Python
python实现批量文件重命名
2019/10/31 Python
python源文件的字符编码知识点详解
2021/03/04 Python
前端实现背景虚化但内容清晰且自适应 的实例代码
2019/08/01 HTML / CSS
幼儿教师思想汇报
2014/01/10 职场文书
四年级语文教学反思
2014/02/05 职场文书
卫生系统先进事迹
2014/05/13 职场文书
专项法律服务方案
2014/06/11 职场文书
四风个人对照检查材料思想汇报(办公室通用版)
2014/10/07 职场文书
矛盾论读书笔记
2015/06/29 职场文书
欢迎新生标语2015
2015/07/16 职场文书
幼儿园小朋友毕业感言
2015/07/30 职场文书
MySQL中order by的使用详情
2021/11/17 MySQL
SQL注入篇学习之盲注/宽字节注入
2022/03/03 MySQL
Nginx安装配置详解
2022/06/25 Servers