這是筆者最近一段時間的學習收穫,將其整理成文,但願能對各位有些許幫助。文章稍長,但絕對精粹。也算是筆者爲各位獻上的一份「元旦大禮」吧~~
(因文章過長,故將其整理爲上、下兩篇,之間連續貫通)javascript
Vue運行機制全局預覽
【初始化及掛載】=>【編譯】(parse-optimize-generate)=>【render function渲染】(響應式)=>【Virtual DOM】=>【更新視圖】html
初始化及掛載
在new了vue以後,Vue會調用_init函數進行(全局)初始化。初始化生命週期、事件、props、methods、data、computed、watch等。其中最重要的是經過Object.defineProperty
設置setter和getter函數,用來實現【響應式】和【依賴收集】。
初始化後調用 $mount 掛載組件 —— 若是是運行時編譯,即不存在 render function 可是存在templete的狀況,(此後)還需進行【編譯】步驟。vue
編譯
java
- parse :用正則等方法解析templete模板中的指令、class、style等數據,造成AST(抽象語法樹——源代碼的抽象語法結構的樹狀表現形式)
- optimize :主要爲了標記static節點。這是vue的一處優化——後面當update更新界面時,會有一個 patch 的過程,diff算法會直接跳過靜態節點,從而減小了比較的過程,優化了patch的性能
- generate :將AST轉化爲 render function 字符串的過程。獲得 render & staticRenderFns 字符串
經歷以上三個階段後,組件中就會存在渲染VNode所需的render function了。react
響應式
Vue的響應式中作出卓越貢獻的也就getter和setter了。
當 render function 被渲染時,由於會讀取所需對象的值,因此會觸發getter進行【依賴收集】,其目的是將觀察者Watcher對象存放到當前閉包中的訂閱者Dep的subs中,造成以下關係:
在修改對象值時,會觸發對應的setter,setter通知以前【依賴收集】獲得的Dep中每個Watcher,告訴他們本身的值改變了,須要從新渲染視圖。這時候這些Watcher就開始調用update來更新視圖 —— 固然,這中間還有一個patch的過程,以及使用隊列來異步更新的策略。web
Virtual DOM
前面說 render function 會轉化爲VNode節點。而Virtual DOM實際上是一棵以JavaScript對象爲基礎的樹。用對象屬性描述節點。它實際上只是一層對真是DOM的抽象。
正是因爲Virtual DOM是以JavaScript對象爲基礎而不依賴平臺環境。因此使他擁有了跨平臺的能力。算法
一個簡單的例子:express
// AST { tag:'div', children:[ { tag:'a', data:{ directives:[ //屬性 { rawName:'v-show', expression:'isShow', name:'show', value:true } ], staticClass:'demo' }, text:'click me' } ] }
渲染以後就獲得:閉包
<div> <a class="demo" v-show="isShow">click me</a> </div>
這兩段代碼的逆向過程,其實就是一個HTML解析器解析標籤的過程。讓咱們來逐步看看:異步
響應式系統
對vue稍有深究的人都必定知道,Vue的【響應式】是基於Object.defineProperty
實現的。
其使用方法:
Object.defineProperty(obj,prop,descriptor) //目標對象、須要操做的目標對象的屬性名、描述符(屬性彙集地)
好比,通常更喜歡這樣寫:
Object.defineProperty(obj,prop,{ enumerable:是否能夠枚舉 configurable:是否能夠被修改或刪除 get: set: })
實現Observer
首先定義一個函數cb,用來模擬視圖更新。內部是一些更新視圖的方法:
function cb(val){ //渲染視圖 console.log('視圖更新了...'); }
而後咱們定義一個defineReactive,這個方法經過Object.defineProperty
來實現對對象的【響應式】化,入參固然是obj(須要綁定的對象)、key(obj的一些屬性)、val(具體值),通過defineReactive處理後,obj的key屬性會在讀的時候觸發reactiveGetter方法,在寫的時候觸發reactiveSetter方法:
function defineReactive(obj,key,val){ Object.defineProperty(obj,key,{ enumable:true, configurable:true, get:function reactiveGetter(){ return val; }, set:function reactiveSetter(newVal){ if(newVal===val) return; cb(newVal); } }); }
這些固然是不夠的。咱們須要在這上面再封裝一層observer —— 它傳入一個value(須要「響應式」化的對象)參數,經過遍歷他全部屬性的方式來對該對象的每個屬性作defineReactive處理:
function observer(value){ if(!value || (typeof value !== 'object')){ return; } Object.keys(value).forEach((key)=>{ defineReactive(value,key,value[key]); }); }
最後,咱們把它們封裝到「vue」中:
class Vue{ constructor(options){ this._data=options.data; observer(this._data); } }
這時,就能夠「隨心所欲了」:
let vm=new Vue({ data:{ test:"I'm mxc" } }); vm._data.test="hello world!"; //「視圖更新了...」
這樣代碼就「完美」了嗎?
響應式系統的依賴收集追蹤原理
爲何要「追蹤」?
假如咱們如今有一個Vue對象:
new Vue({ templete:`<div> <span>{ {text1}}</span> <span>{ {text2}}</span> </div>`, data:{ text1:'text1', text2:'text2', text3:'text3' } });
而後咱們這麼作了:
this.text3="I'm text3";
如上,咱們修改了text3的數據,但視圖中並不須要text3,因此咱們並不須要調用cb函數。
要解決這個問題,就須要大名鼎鼎的【訂閱】 & 【觀察者】模式了:
訂閱者Dep —— 存放Watcher對象 (->其實這能夠說成是一個「消息管理中心」)
class Dep{ constructor(){ this.subs=[] } addSub(sub){ this.subs.push(sub); } //通知全部Watcher更新視圖 notify(){ this.subs.forEach((sub)=>{ sub.update(); }); } }
觀察者Watcher:
class Watcher{ constructor(){ //在new一個Watcher對象時將該對象賦值給Dep.target,在get中會用到 Dep.target=this; } update(){ console.log('視圖更新啦...'); } } Dep.target=null;
接下來要去修改一下defineReactive以及Vue的構造函數,來完成【依賴收集的注入】:(咱們再也不須要「cb」了)
function defineReactive(obj,key,val){ const dep=new Dep(); Object.defineProperty(obj,key,{ enumable:true, configurable:true, get:function reactiveGetter(){ //getter【依賴收集】 dep.addSub(Dep.target); return val; }, set:function reactiveSetter(newVal){ if(newVal===val) return; dep.notify(); } }); }
class Vue({ constructor(options){ this._data=options.data; observer(this._data); new Watcher(); //這裏模擬render的過程,爲了觸發test屬性的get函數 console.log('render',this._data.test); } })
咱們在閉包中增長了一個Dep類的對象,用來收集Watcher對象。在對象被「讀」的時候,會觸發reactiveGetter,把當前Watcher對象(存放在Dep.target中)收集到Dep類中去;以後「寫」的時候,觸發reactiveSetter,通知類Dep調用notify來觸發全部Watcher的update更新對應視圖。
專欄連接(免費):vue實現原理及運行機制分析
本文同步分享在 博客「行舟客」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。