vue源碼——淺析響應(yīng)式原理

又到了學(xué)習(xí)源碼的時間??。

我們都知道vue是一個mvvm框架击敌,數(shù)據(jù)與視圖雙向綁定,所有入門vue的同學(xué)拴事,實現(xiàn)的第一個demo應(yīng)該都是??

<div id="app">
  <span>{{ msg }}</span>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
  var vm = new Vue({
    el: '#app',
    data: {
      msg: 'hello, world!'
    }
  })                    // 此時瀏覽器打印 hello,world!
  vm.msg = 'change msg' // 添加這句之后沃斤, 瀏覽器打印'change msg'
</script>

這種數(shù)據(jù)響應(yīng)式綁定是如何實現(xiàn)的呢? 稍有經(jīng)驗的同學(xué)知道刃宵,是發(fā)布訂閱的設(shè)計模式實現(xiàn)的衡瓶,還知道實現(xiàn)這個模式的關(guān)鍵代碼是Object.defineProperty()。再往下問牲证,可能有人就不知道了哮针。 不知道咱們就學(xué),學(xué)無止境好伐~??从隆。開始正文 ??

準(zhǔn)備工作
核心代碼
  • Observer類诚撵, 代碼注釋中的序號我會一一解釋,如果解釋不對键闺,請指正!??
/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates. 
 */
var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();  // 1. 依賴對象
  this.vmCount = 0;
  def(value, '__ob__', this); // 2.def方法
  if (Array.isArray(value)) {
    if (hasProto) {   // 3. hasProto變量
      protoAugment(value, arrayMethods);  // 4.arrayMethods變量澈驼,5. protoAugment方法
    } else {
      copyAugment(value, arrayMethods, arrayKeys);  //6.copyAugment方法
    }
    this.observeArray(value);  // 7.observeArray方法
  } else {
    this.walk(value);  // 8.walk方法
  }
};

首先我們看一下代碼開始的官方解釋辛燥,我理解的意思是:

當(dāng)目標(biāo)對象被追蹤,觀察者這個方法會將目標(biāo)對象的屬性key轉(zhuǎn)換為getter和setter方法缝其,用來收集依賴和捕獲更新

一個截圖你就明白了~


我在data中隨便寫了一個對象挎塌,打印出來之后可以看到,這個對象中多了get xxx set xxx這種方法内边。這些方法就是觀察者給目標(biāo)對象添加的

  • 依次解釋注釋部分
  1. Dep() 依賴類
var uid = 0;

/**
 * A dep is an observable that can have multiple
 * directives subscribing to it.
 */
var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
};

官方解釋我覺得非沉穸迹晦澀,我理解的意思就是 dep對象上有多個方法漠其。這個對象的作用是為了去檢測數(shù)組的變化嘴高,因為Array類型的變量沒有g(shù)etter setter方法,只能通過__ob__屬性中的dep來收集依賴捕獲更新和屎。

  1. def方法
/**
 * Define a property.
 */
function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}

對原生Object.defineProperty進(jìn)行了封裝

  1. hasProto
var hasProto = '__proto__' in {};

判斷一個空對象中是否有__proto__屬性拴驮。這段代碼我最初以為是判斷當(dāng)前環(huán)境,但是我嘗試了在node環(huán)境下打印空對象柴信,發(fā)現(xiàn)里面也有__proto__屬性套啤。在網(wǎng)上查了也沒有結(jié)果,如果你知道的話随常,請麻煩在評論區(qū)幫我解惑潜沦!

  1. arrayMethods
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);

arrayMethodsArray的子類萄涯。這樣做是為了獲取Array的方法,比如push,slice 等等所有方法唆鸡。

  1. protoAugment()
/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment (target, src) {
  /* eslint-disable no-proto */
  target.__proto__ = src;
  /* eslint-enable no-proto */
}

arrayMethods賦值給目標(biāo)對象窃判。 __proto__這個屬性是所有對象都有的,是瀏覽器實現(xiàn)的喇闸,方便我們查看原型鏈袄琳,MDN上建議這個屬性作為可讀屬性,最好不要直接使用燃乍。

  1. copyAugment
/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment (target, src, keys) {
  for (var i = 0, l = keys.length; i < l; i++) {
    var key = keys[i];
    def(target, key, src[key]);
  }
}

這段代碼是把arrayMethods中的方法依次添加到目標(biāo)對象中

  1. observeArray()
/**
 * Observe a list of Array items.
 */
Observer.prototype.observeArray = function observeArray (items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};

如果目標(biāo)是數(shù)組對象唆樊,遍歷這個數(shù)組,給每個對象注冊觀察者對象(也就是watcher)刻蟹。

  1. walk()
/**
 * Walk through all properties and convert them into
 * getter/setters. This method should only be called when
 * value type is Object.
 */
Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj);
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj, keys[i]);
  }
};

如果目標(biāo)是純對象逗旁, 就給其中的每個屬性添加getter/setter方法

  1. defineReactive$$1()
/**
 * Define a reactive property on an Object.
 */
function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if (getter && !setter) { return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();  // 通知watcher改變, 響應(yīng)式原理
    }
  });
}

走到了這一步舆瘪,才是真正實現(xiàn)了響應(yīng)式片效。核心是dep.notify()

整體解析

有一部分代碼我沒有貼出來,如果感興趣可以去vue源碼中查看英古。

我理解的vue響應(yīng)式的思路大致是

  1. 首先給目標(biāo)加上__ob__屬性淀衣,其值是目標(biāo)本身的值以及dep依賴對象和vmcount
  2. 判斷目標(biāo)是否為數(shù)組,因為數(shù)組變化是無法檢測到的召调,所以特例一個情況膨桥。
  3. 如果目標(biāo)是數(shù)組的話,先把數(shù)組中會改變原數(shù)組的方法取出來唠叛,賦給目標(biāo)只嚣,如果目標(biāo)觸發(fā)了這些方法,說明原數(shù)組改變了艺沼,這樣能側(cè)面反應(yīng)出數(shù)據(jù)是否改變册舞。源碼中不僅僅是如此,還給數(shù)組中的每個值注冊了watcher障般,如果這些值改變了调鲸,也會通知watcher
  4. 如果目標(biāo)是對象,給目標(biāo)綁定getter/setter剩拢。對象的值改變會觸發(fā)notify()线得,通知watcher改變,引起視圖改變
總結(jié)

目前寫下這篇博客徐伐,僅僅是我在閱讀源碼之后寫的贯钩,其中肯定有很多理解不正確的地方,后續(xù)在網(wǎng)上學(xué)習(xí)之后,我會改正角雷。其實我覺得vue實現(xiàn)響應(yīng)式最關(guān)鍵的是Dep對象祸穷, 其中的notify()通知watcher,才實現(xiàn)了響應(yīng)式勺三,但是由于我對Dep對象的理解不深雷滚,所以暫時沒有寫下相關(guān)的代碼,繼續(xù)學(xué)習(xí)吗坚,等我深刻理解之后再回來補(bǔ)充??

路漫漫其修遠(yuǎn)兮祈远,吾將上下而求索。共勉商源。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末车份,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子牡彻,更是在濱河造成了極大的恐慌扫沼,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庄吼,死亡現(xiàn)場離奇詭異缎除,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)总寻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門器罐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人废菱,你說我怎么就攤上這事技矮。” “怎么了殊轴?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長袒炉。 經(jīng)常有香客問我旁理,道長,這世上最難降的妖魔是什么我磁? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任孽文,我火速辦了婚禮,結(jié)果婚禮上夺艰,老公的妹妹穿的比我還像新娘芋哭。我一直安慰自己,他們只是感情好郁副,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布减牺。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拔疚。 梳的紋絲不亂的頭發(fā)上肥隆,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機(jī)與錄音稚失,去河邊找鬼栋艳。 笑死,一個胖子當(dāng)著我的面吹牛句各,可吹牛的內(nèi)容都是我干的吸占。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凿宾,長吁一口氣:“原來是場噩夢啊……” “哼矾屯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起菌湃,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤问拘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惧所,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骤坐,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年下愈,在試婚紗的時候發(fā)現(xiàn)自己被綠了纽绍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡势似,死狀恐怖拌夏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情履因,我是刑警寧澤障簿,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站栅迄,受9級特大地震影響站故,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毅舆,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一西篓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧憋活,春花似錦岂津、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橱乱。三九已至,卻和暖如春赁豆,著一層夾襖步出監(jiān)牢的瞬間仅醇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工魔种, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留析二,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓节预,卻偏偏與公主長得像叶摄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子安拟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容