Vue的響應(yīng)式系統(tǒng)
Vue 最獨特的特性之一,是其非侵入性的響應(yīng)式系統(tǒng)。數(shù)據(jù)模型僅僅是普通的JavaScript 對象,而當(dāng)你修改它們時,視圖會進(jìn)行更新,這使得狀態(tài)管理非常簡單直接,我們可以只關(guān)注數(shù)據(jù)本身,而不用手動處理數(shù)據(jù)到視圖的渲染,避免了繁瑣的 DOM 操作,提高了開發(fā)效率。
vue 的響應(yīng)式系統(tǒng)依賴于三個重要的類:Dep 類、Watcher 類、Observer 類,然后使用發(fā)布訂閱模式的思想將他們?nèi)嗪显谝黄穑ú涣私獍l(fā)布訂閱模式的可以看我之前的文章發(fā)布訂閱模式與觀察者模式)。

Observer
Observe扮演的角色是發(fā)布者,他的主要作用是調(diào)用defineReactive函數(shù),在defineReactive函數(shù)中使用Object.defineProperty 方法對對象的每一個子屬性進(jìn)行數(shù)據(jù)劫持/監(jiān)聽。
部分代碼展示
defineReactive函數(shù),Observe的核心,劫持?jǐn)?shù)據(jù),在setter中向Dep(調(diào)度中心)添加觀察者,在getter中通知觀察者更新。
function defineReactive(obj, key, val, customSetter, shallow){  //監(jiān)聽屬性key  //關(guān)鍵點:在閉包中聲明一個Dep實例,用于保存watcher實例  var dep = new Dep();  var getter = property && property.get;  var setter = property && property.set;    if(!getter && arguments.length === 2) {    val = obj[key];  }  //執(zhí)行observe,監(jiān)聽屬性key所代表的值val的子屬性  var childOb = observe(val);  Object.defineProperty(obj, key, {    enumerable: true,    configurable: true,    get: function reactiveGetter() {      //獲取值      var value = getter ? getter.call(obj) : val;      //依賴收集:如果當(dāng)前有活動的Dep.target(觀察者--watcher實例)      if(Dep.target) {        //將dep放進(jìn)當(dāng)前觀察者的deps中,同時,將該觀察者放入dep中,等待變更通知        dep.depend();        if(childOb) {          //為子屬性進(jìn)行依賴收集          //其實就是將同一個watcher觀察者實例放進(jìn)了兩個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;      }      //新的值需要重新進(jìn)行observe,保證數(shù)據(jù)響應(yīng)式      childOb = observe(newVal);      //關(guān)鍵點:遍歷dep.subs,通知所有的觀察者      dep.notify();    }  });}Dep
Dep 扮演的角色是調(diào)度中心/訂閱器,主要的作用就是收集觀察者Watcher和通知觀察者目標(biāo)更新。每個屬性擁有自己的消息訂閱器dep,用于存放所有訂閱了該屬性的觀察者對象,當(dāng)數(shù)據(jù)發(fā)生改變時,會遍歷觀察者列表(dep.subs),通知所有的watch,讓訂閱者執(zhí)行自己的update邏輯。
部分代碼展示
Dep的設(shè)計比較簡單,就是收集依賴,通知觀察者
//Dep構(gòu)造函數(shù)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() {  //依賴收集:如果當(dāng)前有觀察者,將該dep放進(jìn)當(dāng)前觀察者的deps中  //同時,將當(dāng)前觀察者放入觀察者列表subs中  if(Dep.target) {    Dep.target.addDep(this);  }};Dep.prototype.notify = function notify() {  // 循環(huán)處理,運(yùn)行每個觀察者的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) {  //如果當(dāng)前有正在處理的觀察者,將他壓入待處理隊列  if(Dep.target) {    targetStack.push(Dep.target);  }  //將Dep.target指向需要處理的觀察者  Dep.target = _target;}function popTarget() {  //將Dep.target指向棧頂?shù)挠^察者,并將他移除隊列  Dep.target = targetStack.pop();}Watcher
Watcher扮演的角色是訂閱者/觀察者,他的主要作用是為觀察屬性提供回調(diào)函數(shù)以及收集依賴(如計算屬性computed,vue會把該屬性所依賴數(shù)據(jù)的dep添加到自身的deps中),當(dāng)被觀察的值發(fā)生變化時,會接收到來自dep的通知,從而觸發(fā)回調(diào)函數(shù)。,
部分代碼展示
Watcher類的實現(xiàn)比較復(fù)雜,因為他的實例分為渲染 watcher(render-watcher)、計算屬性 watcher(computed-watcher)、偵聽器 watcher(normal-watcher)三種, 
這三個實例分別是在三個函數(shù)中構(gòu)建的:mountComponent 、initComputed和Vue.prototype.$watch。
normal-watcher:我們在組件鉤子函數(shù)watch 中定義的,都屬于這種類型,即只要監(jiān)聽的屬性改變了,都會觸發(fā)定義好的回調(diào)函數(shù),這類watch的expression是我們寫的回調(diào)函數(shù)的字符串形式。
computed-watcher:我們在組件鉤子函數(shù)computed中定義的,都屬于這種類型,每一個 computed 屬性,最后都會生成一個對應(yīng)的 watcher 對象,但是這類 watcher 有個特點:當(dāng)計算屬性依賴于其他數(shù)據(jù)時,屬性并不會立即重新計算,只有之后其他地方需要讀取屬性的時候,它才會真正計算,即具備 lazy(懶計算)特性。這類watch的expression是計算屬性中的屬性名。
render-watcher:每一個組件都會有一個 render-watcher, 當(dāng) data/computed 中的屬性改變的時候,會調(diào)用該 render-watcher 來更新組件的視圖。這類watch的expression是 function () {vm._update(vm._render(), hydrating);}。
除了功能上的區(qū)別,這三種 watcher 也有固定的執(zhí)行順序,分別是:computed-render -> normal-watcher -> render-watcher。
這樣安排是有原因的,這樣就能盡可能的保證,在更新組件視圖的時候,computed 屬性已經(jīng)是最新值了,如果 render-watcher 排在 computed-render 前面,就會導(dǎo)致頁面更新的時候 computed 值為舊數(shù)據(jù)。
這里我們只看其中一部分代碼
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; //是否啟用深度監(jiān)聽    this.user = !!options.user; //主要用于錯誤處理,偵聽器 watcher的 user為true,其他基本為false    this.lazy = !!options.lazy; //惰性求職,當(dāng)屬于計算屬性watcher時為true    this.sync = !!options.sync; //標(biāo)記為同步計算,三大類型暫無  } else {    this.deep = this.user = this.lazy = this.sync = false;  }  //初始化各種屬性和option    //觀察者的回調(diào)  //除了偵聽器 watcher外,其他大多為空函數(shù)  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  // 當(dāng)是渲染watcher時,expOrFn是updateComponent,即重新渲染執(zhí)行render(_update)  // 當(dāng)是計算watcher時,expOrFn是計算屬性的計算方法  // 當(dāng)是偵聽器watcher時,expOrFn是watch屬性的名字,this.cb就是watch的handler屬性    //對于渲染watcher和計算watcher來說,expOrFn的值是一個函數(shù),可以直接設(shè)置getter  //對于偵聽器watcher來說,expOrFn是watch屬性的名字,會使用parsePath函數(shù)解析路徑,獲取組件上該屬性的值(運(yùn)行g(shù)etter)    //依賴(訂閱目標(biāo))更新,執(zhí)行update,會進(jìn)行取值操作,運(yùn)行watcher.getter,也就是expOrFn函數(shù)  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設(shè)置為該觀察者  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);    }  }};//當(dāng)一個依賴改變的時候,通知它updateWatcher.prototype.update = function update() {  //三種watcher,只有計算屬性 watcher的lazy設(shè)置了true,表示啟用惰性求值  if(this.lazy) {    this.dirty = true;  } else if(this.sync) {    //標(biāo)記為同步計算的直接運(yùn)行run,三大類型暫無,所以基本會走下面的queueWatcher    this.run();  } else {    //將watcher推入觀察者隊列中,下一個tick時調(diào)用。    //也就是數(shù)據(jù)變化不是立即就去更新的,而是異步批量去更新的    queueWatcher(this);  }};//update執(zhí)行后,運(yùn)行回調(diào)cbWatcher.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;      //運(yùn)行 cb 函數(shù),這個函數(shù)就是之前傳入的watch中的handler回調(diào)函數(shù)      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);      }    }  }};//對于計算屬性,當(dāng)取值計算屬性時,發(fā)現(xiàn)計算屬性的watcher的dirty是true//說明數(shù)據(jù)不是最新的了,需要重新計算,這里就是重新計算計算屬性的值。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();  }};總結(jié)
Observe是對數(shù)據(jù)進(jìn)行監(jiān)聽,Dep是一個訂閱器,每一個被監(jiān)聽的數(shù)據(jù)都有一個Dep實例,Dep實例里面存放了N多個訂閱者(觀察者)對象watcher。
被監(jiān)聽的數(shù)據(jù)進(jìn)行取值操作時(getter),如果存在Dep.target(某一個觀察者),則說明這個觀察者是依賴該數(shù)據(jù)的(如計算屬性中,計算某一屬性會用到其他已經(jīng)被監(jiān)聽的數(shù)據(jù),就說該屬性依賴于其他屬性,會對其他屬性進(jìn)行取值),就會把這個觀察者添加到該數(shù)據(jù)的訂閱器subs里面,留待后面數(shù)據(jù)變更時通知(會先通過觀察者id判斷訂閱器中是否已經(jīng)存在該觀察者),同時該觀察者也會把該數(shù)據(jù)的訂閱器dep添加到自身deps中,方便其他地方使用。
被監(jiān)聽的數(shù)據(jù)進(jìn)行賦值操作時(setter)時,就會觸發(fā)dep.notify(),循環(huán)該數(shù)據(jù)訂閱器中的觀察者,進(jìn)行更新操作。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。
新聞熱點
疑難解答