1. 程式人生 > 程式設計 >vue $mount 和 el的區別說明

vue $mount 和 el的區別說明

兩者在使用效果上沒有任何區別,都是為了將例項化後的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,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的區別說明就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。