關(guān)于Vue實例創(chuàng)建的整體流程
前言
本篇是分析new Vue()過程相關(guān)的流程,主要了解如下兩點:
- new Vue()整體處理流程
- 掛載處理流程
整體邏輯
使用Vue構(gòu)造函數(shù)創(chuàng)建Vue實例,具體的處理邏輯如下圖:
從上圖中可以看出,Vue實例的具體處理是定義相關(guān)的內(nèi)部屬性,處理實例選項props、data、methods、computed、watch,以及beforeCreate、created生命周期函數(shù)的執(zhí)行。
這里每一個選項都有不少實現(xiàn)邏輯,這里先不展開,之后會有專門的文章細究。
實際上面邏輯流程中還存在一些細節(jié)點,例如$options的差異處理等,這些需要針對特定的場景來具體聊才能更深的理解,這里先暫時不展開。
el或$mount掛載
對于掛載的處理是在_init即Vue()構(gòu)造函數(shù)中調(diào)用的,具體的處理就是調(diào)用$mount方法,如下:
// el就是掛載點 if (vm.$options.el) vm.$mount(vm.$options.el);
而$mount是定義在Vue原型上的實例方法,這也解釋了Vue掛載的兩種方式:
- el形式,例如: new Vue({ el: ‘#app’})
- $mount形式,例如: new Vue().$mount(‘#app’)
$mount方法中具體處理如下:
從上面可以得出知曉下面幾個結(jié)論:
- el和$mount兩種方式掛載的緣由,實際上el本質(zhì)上還是調(diào)用$mount方法
- 若render函數(shù)存在,源碼中會將template/el 轉(zhuǎn)換為render函數(shù)
- 掛載點應(yīng)該是普通的html元素,而不應(yīng)該是html或body這種特殊的
- 如果template不存在,實際上會獲取包含掛載點在內(nèi)的outerHTML部分作為template
$mount內(nèi)部是調(diào)用mountComponent函數(shù),該函數(shù)的具體處理是什么呢?具體處理邏輯如下圖:
從上圖邏輯處理中可知:
- mountComponent中實際上處理beforeMount、mounted
- 每個Vue實例都會有相應(yīng)的Watcher實例對應(yīng),Watcher構(gòu)造函數(shù)中vm._watcher和vm._watchers記錄了當(dāng)前實例和watcher對象集合
全局Watcher的具體邏輯
每一個vue實例都對應(yīng)一個watcher實例,這個watcher就是在掛載階段創(chuàng)建的,負責(zé)當(dāng)前實例對應(yīng)的視圖渲染。
其主要邏輯如下:
var updateComponent = function () { vm._update(vm._render(), hydrating); }; new Watcher(vm, updateComponent, noop, { before: function before() { if (vm._isMounted || !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true)
下面是Watcher構(gòu)造函數(shù)中涉及到的主要邏輯:
var Watcher = function Watcher ( vm, expOrFn, cb, options, isRenderWatcher ) { this.vm = vm; if (isRenderWatcher) { vm._watcher = this; } // 收集當(dāng)前實例所有watcher對象 vm._watchers.push(this); // options主要處理 if (options) { this.computed = !!options.computed; } else { this.deep = this.user = this.computed = this.sync = false; } this.dirty = this.computed; // for computed watchers this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); if (typeof expOrFn === 'function') { this.getter = expOrFn; } if (this.computed) { this.value = undefined; this.dep = new Dep(); } else { this.value = this.get(); } };
watcher對象創(chuàng)建的過程中,有幾點邏輯需要主要關(guān)注:
- 每一個vue都對應(yīng)一個watcher實例,該watcher實例負責(zé)視圖渲染,可以通過_watcher屬性得知
// 是否是渲染W(wǎng)atcher對象 if (isRenderWatcher) { vm._watcher = this; }
- 計算屬性的標(biāo)識即computed
- 基于computed的不同操作,即get方法是否立即執(zhí)行,非計算屬性的直接就調(diào)用了this.get()方法
Watcher對象中g(shù)et方法是非常重要的邏輯,其主要邏輯可以歸納如下:
Watcher.prototype.get = function get () { pushTarget(this); var value; var vm = this.vm; try { value = this.getter.call(vm, vm); } catch (e) { // 相關(guān)代碼 } finally { // 支持Watcher對象 if (this.deep) { traverse(value); } popTarget(); this.cleanupDeps(); } return value };
實際通過get方法的邏輯可以得到幾個主要邏輯:
- pushTarget和PopTarget的設(shè)置
- 執(zhí)行對應(yīng)的getter函數(shù)
- watch deep選項的支持邏輯
對于全局Watcher,這里的getter函數(shù)就是執(zhí)行其對應(yīng)的render函數(shù)渲染出來視圖。而watch deep選項則是Vue watch API的使用內(nèi)容,這里暫不展開。
實際上最主要的就是pushTarget相關(guān)的邏輯了,實際上這涉及到一個非常重要的屬性Dep.target。
Dep.target
Dep.target,該屬性是依賴收集關(guān)鍵點之一。
通過Vue源碼知道,Dep.target的改變途徑只有兩個,即pushTarget和popTarget。
function pushTarget(target) { if (Dep.target) targetStack.push(Dep.target); Dep.target = target; } function popTarget() { Dep.target = targetStack.pop(); }
而Vue data中每個屬性的獲取都會檢驗Dep.target是否存在。
只有Dep.target存在下才會去調(diào)用dep.depend()方法來做相關(guān)的處理,而pushTarget和popTarget就是關(guān)鍵了。
Vue源碼中調(diào)用pushTarget函數(shù)實際上就4處:
- handleError:處理錯誤情況的方法
- callHook:生命周期函數(shù)調(diào)用
- Watcher.prototype.get:Watcher對象get實例方法
- getData:初始化data時當(dāng)data為函數(shù)會調(diào)用getData方法
從上面的pushTarget函數(shù)的邏輯可知會傳遞target參數(shù),pushTarget會保存當(dāng)前Dep.target到數(shù)組(模擬棧結(jié)構(gòu))中。
上面4處調(diào)用pushTarget只有一處傳遞的非空的target,即Watcher.prototype.get。
對于Dep.target需要注意的是:
- Dep.target只有三種值:null、undefined、watcher對象
- pushTarget和popTarget是改變Dep.target的唯一途徑
而通過pushTarget可以知道targetStack只會保存watcher對象。通過Vue源碼可知pushTarget和popTarget總是一起搭配出現(xiàn)的,實際上主要作用就是:
在使用pushTarget的地方臨時修改Dep.target的值,已滿足相關(guān)條件
明確下這里的相關(guān)條件,實際上通過源碼中Dep.target使用位置來判斷是可以清晰得到結(jié)論的,即:
Dep.target判斷操作共有3處:
- defineReactive的get函數(shù)中
- Watcher.prototype.depend
- Dep.prototype.depend
從上面可知Dep.target是為watcher對象服務(wù)的,實際上通過Vue響應(yīng)式原理的描述(Dep與Watcher是相關(guān)關(guān)聯(lián)的從而構(gòu)成響應(yīng)式非常重要的一部分),也可以證明Dep.target指向Watcher對象。
Watcher.prototype.get是唯一傳遞了非空target了,而Dep.target值也可能為null、undefined。而=非watcher之外的Dep.target的操作都將Dep.target臨時設(shè)置為undefined,是為了避免執(zhí)行涉及到watcher相關(guān)邏輯。
結(jié)合整體代碼可知:
當(dāng)執(zhí)行g(shù)et函數(shù)時,Dep.target始終是對應(yīng)的watcher對象
總結(jié)
通過對Vue實例創(chuàng)建過程的整體分析,可知其主要的處理邏輯:
- 執(zhí)行Vue構(gòu)造函數(shù),開始初始化工作
- 創(chuàng)建相關(guān)實例屬性,例如options、uid
- 相關(guān)選項初始化工作,例如狀態(tài)(data選項、computed、methods等)、事件相關(guān)等
- 執(zhí)行beforeCreate、created生命周期函數(shù)
- el和$mount兩種掛載背后的處理
$mount -> mountComponent -> new Watcher() -> _render()執(zhí)行渲染視圖
每一個Vue實例都對應(yīng)一個watcher實例,該watcher實例是用于控制視圖渲染的
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue-admin-box第一步npm?install時報錯的處理
這篇文章主要介紹了vue-admin-box第一步npm?install時報錯的處理方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10Vue3中的defineExpose函數(shù)用法深入解析
這篇文章主要介紹了Vue3中的defineExpose函數(shù)用法的相關(guān)資料,defineExpose是Vue3中用于在模式下暴露組件內(nèi)部屬性和方法的輔助函數(shù),它允許父組件通過ref訪問子組件的暴露內(nèi)容,提高組件間的交互能力并保持封裝性,需要的朋友可以參考下2025-01-01詳解windows下vue-cli及webpack 構(gòu)建網(wǎng)站(二)導(dǎo)入bootstrap樣式
這篇文章主要介紹了詳解windows下vue-cli及webpack 構(gòu)建網(wǎng)站(二)導(dǎo)入bootstrap樣式,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06