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 相关文章推荐
使用prototype.js进行异步操作
Feb 07 Javascript
javascript new fun的执行过程
Aug 05 Javascript
自写简单JS判断是否已经弹出页面
Oct 20 Javascript
JS Pro-深入面向对象的程序设计之继承的详解
May 07 Javascript
JQuery插件开发示例代码
Nov 06 Javascript
jquery实现图片滚动效果的简单实例
Nov 23 Javascript
将list转换为json失败的原因
Dec 17 Javascript
JS中Swiper的使用和轮播图效果
Aug 11 Javascript
JavaScript编写的网页小游戏,很给力
Aug 18 Javascript
javascript  删除select中的所有option的实例
Sep 17 Javascript
node.js+express+mySQL+ejs+bootstrop实现网站登录注册功能
Jan 12 Javascript
微信小程序中使用自定义图标(阿里icon)的方法
Aug 20 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
使用XDebug调试及单元测试覆盖率分析
2011/01/27 PHP
使用php+swoole对client数据实时更新(一)
2016/01/07 PHP
swoole和websocket简单聊天室开发
2017/11/18 PHP
Thinkphp5框架简单实现钩子(Hook)行为的方法示例
2019/09/03 PHP
js跑步算法的实现代码
2013/12/04 Javascript
JS执行删除前的判断代码
2014/02/18 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
JavaScript控制按钮可用或不可用的方法
2015/04/03 Javascript
jQuery实现自动滚动到页面顶端的方法
2015/05/22 Javascript
JQuery标签页效果的两个实例讲解(4)
2015/09/17 Javascript
form+iframe解决跨域上传文件的方法
2016/11/18 Javascript
探索Javascript中this的奥秘
2016/12/11 Javascript
JS正则表达式判断有效数实例代码
2017/03/13 Javascript
NodeJS、NPM安装配置步骤(windows版本) 以及环境变量详解
2017/05/13 NodeJs
简化vuex的状态管理方案的方法
2018/06/02 Javascript
JavaScript对象拷贝与Object.assign用法实例分析
2018/06/20 Javascript
深入浅析Node.js 事件循环、定时器和process.nextTick()
2018/10/22 Javascript
详释JavaScript执行环境与执行栈
2019/04/02 Javascript
JS html事件冒泡和事件捕获操作示例
2019/05/01 Javascript
微信小程序 动态修改页面数据及参数传递过程详解
2019/09/27 Javascript
微信内置开发 iOS修改键盘换行为搜索的解决方案
2019/11/06 Javascript
python实现JAVA源代码从ANSI到UTF-8的批量转换方法
2015/08/10 Python
使用python3.5仿微软记事本notepad
2016/06/15 Python
python机器学习库xgboost的使用
2020/01/20 Python
python实现超级玛丽游戏
2020/03/18 Python
使用OpenCV获取图片连通域数量,并用不同颜色标记函
2020/06/04 Python
详解python 支持向量机(SVM)算法
2020/09/18 Python
美国职棒大联盟官方网上商店:MLBShop.com
2017/11/12 全球购物
英国在线药房:Express Chemist
2019/03/28 全球购物
香港中原电器网上商店:Chung Yuen
2019/06/26 全球购物
农村婚庆司仪主持词
2014/03/15 职场文书
安全负责人任命书
2014/06/06 职场文书
毕业生应聘求职信
2014/07/10 职场文书
保送生自荐信
2015/03/06 职场文书
结婚堵门保证书
2015/05/08 职场文书
汽车修理厂管理制度
2015/08/05 职场文书