Vue 最獨特的特性之一,是其非侵入性的響應式系統。數據模型僅僅是普通的JavaScript 對象,而當你修改它們時,視圖會進行更新,這使得狀態管理很是簡單直接,咱們能夠只關注數據自己,而不用手動處理數據到視圖的渲染,避免了繁瑣的 DOM 操做,提升了開發效率。vue
vue 的響應式系統依賴於三個重要的類:Dep 類、Watcher 類、Observer 類,而後使用發佈訂閱模式的思想將他們揉合在一塊兒(不瞭解發佈訂閱模式的能夠看我以前的文章發佈訂閱模式與觀察者模式)。react
Observe扮演的角色是發佈者,他的主要做用是調用defineReactive函數,在defineReactive函數中使用Object.defineProperty 方法對對象的每個子屬性進行數據劫持/監聽。express
部分代碼展現segmentfault
defineReactive函數,Observe的核心,劫持數據,在setter中向Dep(調度中心)添加觀察者,在getter中通知觀察者更新。閉包
function defineReactive(obj, key, val, customSetter, shallow){ //監聽屬性key //關鍵點:在閉包中聲明一個Dep實例,用於保存watcher實例 var dep = new Dep(); var getter = property && property.get; var setter = property && property.set; if(!getter && arguments.length === 2) { val = obj[key]; } //執行observe,監聽屬性key所表明的值val的子屬性 var childOb = observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { //獲取值 var value = getter ? getter.call(obj) : val; //依賴收集:若是當前有活動的Dep.target(觀察者--watcher實例) if(Dep.target) { //將dep放進當前觀察者的deps中,同時,將該觀察者放入dep中,等待變動通知 dep.depend(); if(childOb) { //爲子屬性進行依賴收集 //其實就是將同一個watcher觀察者實例放進了兩個dep中 //一個是正在自己閉包中的dep,另外一個是子屬性的dep childOb.dep.depend(); } } return value }, set: function reactiveSetter(newVal) { //獲取value var value = getter ? getter.call(obj) : val; if(newVal === value || (newVal !== newVal && value !== value)) { return } if(setter) { setter.call(obj, newVal); } else { val = newVal; } //新的值須要從新進行observe,保證數據響應式 childOb = observe(newVal); //關鍵點:遍歷dep.subs,通知全部的觀察者 dep.notify(); } }); }
Dep 扮演的角色是調度中心/訂閱器,主要的做用就是收集觀察者Watcher和通知觀察者目標更新。每一個屬性擁有本身的消息訂閱器dep,用於存放全部訂閱了該屬性的觀察者對象,當數據發生改變時,會遍歷觀察者列表(dep.subs),通知全部的watch,讓訂閱者執行本身的update邏輯。異步
部分代碼展現函數
Dep的設計比較簡單,就是收集依賴,通知觀察者ui
//Dep構造函數 var Dep = function Dep() { this.id = uid++; this.subs = []; }; //向dep的觀察者列表subs添加觀察者 Dep.prototype.addSub = function addSub(sub) { this.subs.push(sub); }; //從dep的觀察者列表subs移除觀察者 Dep.prototype.removeSub = function removeSub(sub) { remove(this.subs, sub); }; Dep.prototype.depend = function depend() { //依賴收集:若是當前有觀察者,將該dep放進當前觀察者的deps中 //同時,將當前觀察者放入觀察者列表subs中 if(Dep.target) { Dep.target.addDep(this); } }; Dep.prototype.notify = function notify() { // 循環處理,運行每一個觀察者的update接口 var subs = this.subs.slice(); for(var i = 0, l = subs.length; i < l; i++) { subs[i].update(); } }; //Dep.target是觀察者,這是全局惟一的,由於在任什麼時候候只有一個觀察者被處理。 Dep.target = null; //待處理的觀察者隊列 var targetStack = []; function pushTarget(_target) { //若是當前有正在處理的觀察者,將他壓入待處理隊列 if(Dep.target) { targetStack.push(Dep.target); } //將Dep.target指向須要處理的觀察者 Dep.target = _target; } function popTarget() { //將Dep.target指向棧頂的觀察者,並將他移除隊列 Dep.target = targetStack.pop(); }
Watcher扮演的角色是訂閱者/觀察者,他的主要做用是爲觀察屬性提供回調函數以及收集依賴(如計算屬性computed,vue會把該屬性所依賴數據的dep添加到自身的deps中),當被觀察的值發生變化時,會接收到來自dep的通知,從而觸發回調函數。,this
部分代碼展現lua
Watcher類的實現比較複雜,由於他的實例分爲渲染 watcher(render-watcher)、計算屬性 watcher(computed-watcher)、偵聽器 watcher(normal-watcher)三種,
這三個實例分別是在三個函數中構建的:mountComponent 、initComputed和Vue.prototype.$watch。
normal-watcher:咱們在組件鉤子函數watch 中定義的,都屬於這種類型,即只要監聽的屬性改變了,都會觸發定義好的回調函數,這類watch的expression是咱們寫的回調函數的字符串形式。
computed-watcher:咱們在組件鉤子函數computed中定義的,都屬於這種類型,每個 computed 屬性,最後都會生成一個對應的 watcher 對象,可是這類 watcher 有個特色:當計算屬性依賴於其餘數據時,屬性並不會當即從新計算,只有以後其餘地方須要讀取屬性的時候,它纔會真正計算,即具有 lazy(懶計算)特性。這類watch的expression是計算屬性中的屬性名。
render-watcher:每個組件都會有一個 render-watcher, 當 data/computed 中的屬性改變的時候,會調用該 render-watcher 來更新組件的視圖。這類watch的expression是 function () {vm._update(vm._render(), hydrating);}
。
除了功能上的區別,這三種 watcher 也有固定的執行順序,分別是:computed-render -> normal-watcher -> render-watcher
。
這樣安排是有緣由的,這樣就能儘量的保證,在更新組件視圖的時候,computed 屬性已是最新值了,若是 render-watcher 排在 computed-render 前面,就會致使頁面更新的時候 computed 值爲舊數據。
這裏咱們只看其中一部分代碼
function Watcher(vm, expOrFn, cb, options, isRenderWatcher) { this.vm = vm; if(isRenderWatcher) { vm._watcher = this; } vm._watchers.push(this); // options if(options) { this.deep = !!options.deep; //是否啓用深度監聽 this.user = !!options.user; //主要用於錯誤處理,偵聽器 watcher的 user爲true,其餘基本爲false this.lazy = !!options.lazy; //惰性求職,當屬於計算屬性watcher時爲true this.sync = !!options.sync; //標記爲同步計算,三大類型暫無 } else { this.deep = this.user = this.lazy = this.sync = false; } //初始化各類屬性和option //觀察者的回調 //除了偵聽器 watcher外,其餘大多爲空函數 this.cb = cb; this.id = ++uid$1; // uid for batching this.active = true; this.dirty = this.lazy; // for lazy watchers this.deps = []; this.newDeps = []; this.depIds = new _Set(); this.newDepIds = new _Set(); this.expression = expOrFn.toString(); // 解析expOrFn,賦值給this.getter // 當是渲染watcher時,expOrFn是updateComponent,即從新渲染執行render(_update) // 當是計算watcher時,expOrFn是計算屬性的計算方法 // 當是偵聽器watcher時,expOrFn是watch屬性的名字,this.cb就是watch的handler屬性 //對於渲染watcher和計算watcher來講,expOrFn的值是一個函數,能夠直接設置getter //對於偵聽器watcher來講,expOrFn是watch屬性的名字,會使用parsePath函數解析路徑,獲取組件上該屬性的值(運行getter) //依賴(訂閱目標)更新,執行update,會進行取值操做,運行watcher.getter,也就是expOrFn函數 if(typeof expOrFn === 'function') { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); } this.value = this.lazy ? undefined : this.get(); }; //取值操做 Watcher.prototype.get = function get() { //Dep.target設置爲該觀察者 pushTarget(this); var vm = this.vm; //取值 var value = this.getter.call(vm, vm); //移除該觀察者 popTarget(); return value }; Watcher.prototype.addDep = function addDep(dep) { var id = dep.id; if(!this.newDepIds.has(id)) { //爲觀察者的deps添加依賴dep this.newDepIds.add(id); this.newDeps.push(dep); if(!this.depIds.has(id)) { //爲dep添加該觀察者 dep.addSub(this); } } }; //當一個依賴改變的時候,通知它update Watcher.prototype.update = function update() { //三種watcher,只有計算屬性 watcher的lazy設置了true,表示啓用惰性求值 if(this.lazy) { this.dirty = true; } else if(this.sync) { //標記爲同步計算的直接運行run,三大類型暫無,因此基本會走下面的queueWatcher this.run(); } else { //將watcher推入觀察者隊列中,下一個tick時調用。 //也就是數據變化不是當即就去更新的,而是異步批量去更新的 queueWatcher(this); } }; //update執行後,運行回調cb Watcher.prototype.run = function run() { if(this.active) { var value = this.get(); if( value !== this.value || isObject(value) || this.deep ) { var oldValue = this.value; this.value = value; //運行 cb 函數,這個函數就是以前傳入的watch中的handler回調函數 if(this.user) { try { this.cb.call(this.vm, value, oldValue); } catch(e) { handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\"")); } } else { this.cb.call(this.vm, value, oldValue); } } } }; //對於計算屬性,當取值計算屬性時,發現計算屬性的watcher的dirty是true //說明數據不是最新的了,須要從新計算,這裏就是從新計算計算屬性的值。 Watcher.prototype.evaluate = function evaluate() { this.value = this.get(); this.dirty = false; }; //收集依賴 Watcher.prototype.depend = function depend() { var this$1 = this; var i = this.deps.length; while(i--) { this$1.deps[i].depend(); } };
Observe是對數據進行監聽,Dep是一個訂閱器,每個被監聽的數據都有一個Dep實例,Dep實例裏面存放了N多個訂閱者(觀察者)對象watcher。
被監聽的數據進行取值操做時(getter),若是存在Dep.target(某一個觀察者),則說明這個觀察者是依賴該數據的(如計算屬性中,計算某一屬性會用到其餘已經被監聽的數據,就說該屬性依賴於其餘屬性,會對其餘屬性進行取值),就會把這個觀察者添加到該數據的訂閱器subs裏面,留待後面數據變動時通知(會先經過觀察者id判斷訂閱器中是否已經存在該觀察者),同時該觀察者也會把該數據的訂閱器dep添加到自身deps中,方便其餘地方使用。
被監聽的數據進行賦值操做時(setter)時,就會觸發dep.notify(),循環該數據訂閱器中的觀察者,進行更新操做。