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 相关文章推荐
jQuery制作简洁的多级联动Select下拉框
Dec 23 Javascript
javascript实现检验的各种规则
Jul 31 Javascript
深入解析Javascript闭包的功能及实现方法
Jul 10 Javascript
Bootstrap基本布局实现方法详解
Nov 25 Javascript
js通过keyCode值判断单击键盘上某个键,然后触发指定的事件方法
Feb 19 Javascript
JavaScript实现百度搜索框效果
Mar 26 Javascript
微信小程序自定义prompt组件步骤详解
Jun 12 Javascript
JavaScript数据结构与算法之二叉树遍历算法详解【先序、中序、后序】
Feb 21 Javascript
Vue框架下引入ActiveX控件的问题解决
Mar 25 Javascript
axios如何取消重复无用的请求详解
Dec 15 Javascript
vue elementui 实现搜索栏公共组件封装的实例代码
Jan 20 Javascript
vue 解决IOS10低版本白屏的问题
Nov 17 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
使用Apache的rewrite技术
2006/06/22 PHP
实现树状结构的两种方法
2006/10/09 PHP
php面向对象全攻略 (十二) 抽象方法和抽象类
2009/09/30 PHP
PHP学习笔记(一) 简单了解PHP
2014/08/04 PHP
php函数serialize()与unserialize()用法实例
2014/11/06 PHP
PHP解压tar.gz格式文件的方法
2016/02/14 PHP
PHP中ajax无刷新上传图片与图片下载功能
2017/02/21 PHP
PHP计算近1年的所有月份
2017/03/13 PHP
Laravel使用模型实现like模糊查询的例子
2019/10/24 PHP
javascript中interval与setTimeOut的区别示例介绍
2014/03/14 Javascript
JS 实现列表与多选框选择附预览动画
2014/10/29 Javascript
省市二级联动小案例讲解
2016/07/24 Javascript
vue-router 学习快速入门
2017/03/01 Javascript
Angularjs修改密码的实例代码
2017/05/26 Javascript
Vue.js框架路由使用方法实例详解
2017/08/25 Javascript
关于angular引入ng-zorro的问题浅析
2020/09/09 Javascript
[32:47]完美世界DOTA2联赛 GXR vs IO 第二场 11.07
2020/11/09 DOTA
[36:16]完美世界DOTA2联赛PWL S3 access vs Rebirth 第一场 12.19
2020/12/24 DOTA
Python群发邮件实例代码
2014/01/03 Python
Python EOL while scanning string literal问题解决方法
2020/09/18 Python
python数字图像处理之骨架提取与分水岭算法
2018/04/27 Python
python输出决策树图形的例子
2019/08/09 Python
python实现简易淘宝购物
2019/11/22 Python
Python图像处理库PIL的ImageEnhance模块使用介绍
2020/02/26 Python
Python捕获异常堆栈信息的几种方法(小结)
2020/05/18 Python
Python脚本实现Zabbix多行日志监控过程解析
2020/08/26 Python
谈谈对css属性box-sizing的了解
2017/01/04 HTML / CSS
Speedo澳大利亚官网:全球领先游泳品牌
2018/02/04 全球购物
西班牙Polo衫品牌:Polo Club
2020/08/09 全球购物
企业文化理念标语
2014/06/10 职场文书
优秀教师先进事迹材料
2014/12/15 职场文书
工程部部长岗位职责
2015/02/12 职场文书
狼牙山五壮士观后感
2015/06/09 职场文书
《巨人的花园》教学反思
2016/02/19 职场文书
ThinkPHP5和ThinkPHP6的区别
2021/03/31 PHP
IDEA使用SpringAssistant插件创建SpringCloud项目
2021/06/23 Java/Android