vue源碼分析之響應(yīng)式原理(Watcher、Observer蚁鳖、Dep)

vue作為最受歡迎的前端開發(fā)框架磺芭。非常值得我們傾心研究一番。

讀源碼的動(dòng)力

  • 源碼閱讀可以看到作者(前端技術(shù)最頂端的人)對(duì)js的理解
  • 可以看到作者優(yōu)秀的設(shè)計(jì)思想
  • 可以更加快速的處理和理解我們?cè)谌粘9ぷ鞒霈F(xiàn)的問題
  • 提高自己的技術(shù)深度和廣度

Vue響應(yīng)式原理

  1. 使用Object.defineProperty將data數(shù)據(jù)變成響應(yīng)式對(duì)象醉箕,通過Observer給對(duì)象添加get 和 set屬性
  2. 調(diào)用對(duì)象數(shù)據(jù)時(shí)會(huì)觸發(fā)getter钾腺,改變對(duì)象數(shù)據(jù)時(shí)會(huì)觸發(fā)setter
  3. 在getter中進(jìn)行依賴收集(使用到當(dāng)前數(shù)據(jù)的地方會(huì)被作為一個(gè)Watcher對(duì)象處理)
  4. 在setter中通過Dep進(jìn)行派發(fā)更新(通過處理watcher對(duì)象Dep.target = watcher從而調(diào)用nextTick進(jìn)行數(shù)據(jù)更新)

通過Observer處理響應(yīng)式對(duì)象

  1. 初始化Vue實(shí)例時(shí)調(diào)用initState(this)
  2. 在initState(this)函數(shù)中調(diào)用new Observer(vm.$options.data)處理data數(shù)據(jù)
  3. 在Observer(val)中通過調(diào)用walk()循環(huán)data數(shù)據(jù)并調(diào)用defineReactive(obj, key, val)實(shí)現(xiàn)響應(yīng)式綁定
Vue.prototype._init = function() {
    initState(this)
}

function initState(vm) {
  new Observer(vm.$options.data)
}

class Observer {
  constructor (value) {
    this.walk(value)
  }
  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i], obj[keys[i]])
    }
  }
}

function defineReactive (obj, key, val) {
  val = obj[key]
  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      return value
    },
    set: function reactiveSetter (newVal) {
      val = newVal
    }
  })
}

給對(duì)象添加getter和setter屬性

  1. 利用Object.defineProperty函數(shù)的特性給對(duì)象添加get和set屬性
  2. 監(jiān)測(cè)getter和setter是否存在,不存在就添加get/set屬性
function defineReactive (obj, key, val) {
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }
  const getter = property && property.get
  const setter = property && property.set
  Object.defineProperty(obj, key, {
    get: function reactiveGetter () {
      return value
    },
    set: function reactiveSetter (newVal) {
      val = newVal
    }
  })
}

利用Dep在get中進(jìn)行依賴收集

  1. Dep是Watcher的管理器
  2. Dep.target就是當(dāng)前的Watcher
  3. depend()中會(huì)調(diào)用執(zhí)行Dep.target.addDep(this)
  4. 通過一連串的函數(shù)調(diào)用最終將當(dāng)前的watcher存儲(chǔ)在subs[]中
  let childOb = !shallow && observe(val)
  get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
      dep.depend()
      if (childOb) {
        childOb.dep.depend()
      }
    }
    return value
  }
  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  addDep (dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

在set中進(jìn)行派發(fā)更新

  1. set函數(shù)會(huì)在修改當(dāng)前data數(shù)據(jù)時(shí)調(diào)用
  2. set中會(huì)執(zhí)行dep.notify()函數(shù) -- 派發(fā)更新的重點(diǎn)在notify()中
  3. notify會(huì)循環(huán)subs得到之前在get中存儲(chǔ)的Watcher并調(diào)用update()
  4. update中會(huì)執(zhí)行Watcher的run函數(shù)
  5. run函數(shù)中執(zhí)行this.cb.call(this.vm, value, oldValue)讥裤,最終會(huì)觸發(fā)updateComponent函數(shù)進(jìn)行Dom更新
    set: function reactiveSetter (newVal) {
      const 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()
    }
    notify () {
      const subs = this.subs.slice()
      for (let i = 0, l = subs.length; i < l; i++) {
        subs[i].update()
      }
    }
    update () {
      if (this.lazy) {
        this.dirty = true
      } else if (this.sync) {
        this.run()
      } else {
        queueWatcher(this)
      }
    }
    run () {
      this.cb.call(this.vm, value, oldValue)
    }

Watcher是什么時(shí)候創(chuàng)建的放棒?Watcher是如何觸發(fā)Dom更新的

  1. 在初始化虛擬Dom時(shí)initVirtualComponent()函數(shù)中會(huì)執(zhí)行 new Watcher(vm, updateComponent, noop, null, true)創(chuàng)建Watcher
  2. Watcher對(duì)象的cb數(shù)據(jù)updateComponent函數(shù),執(zhí)行this.cb.call(this.vm, value, oldValue)也就是執(zhí)行了updateComponent
  3. updateComponent()中調(diào)用_update()己英,進(jìn)而更新Dom
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末间螟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌寒亥,老刑警劉巖邮府,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異溉奕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)忍啤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門加勤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人同波,你說我怎么就攤上這事鳄梅。” “怎么了未檩?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵戴尸,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我冤狡,道長(zhǎng)孙蒙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任悲雳,我火速辦了婚禮挎峦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘合瓢。我一直安慰自己坦胶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布晴楔。 她就那樣靜靜地躺著顿苇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪税弃。 梳的紋絲不亂的頭發(fā)上纪岁,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音钙皮,去河邊找鬼蜂科。 笑死,一個(gè)胖子當(dāng)著我的面吹牛短条,可吹牛的內(nèi)容都是我干的导匣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼茸时,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼贡定!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起可都,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤缓待,失蹤者是張志新(化名)和其女友劉穎蚓耽,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旋炒,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡步悠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘫镇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鼎兽。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铣除,靈堂內(nèi)的尸體忽然破棺而出谚咬,到底是詐尸還是另有隱情,我是刑警寧澤尚粘,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布择卦,位于F島的核電站,受9級(jí)特大地震影響郎嫁,放射性物質(zhì)發(fā)生泄漏秉继。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一行剂、第九天 我趴在偏房一處隱蔽的房頂上張望秕噪。 院中可真熱鬧,春花似錦厚宰、人聲如沸腌巾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澈蝙。三九已至,卻和暖如春撵幽,著一層夾襖步出監(jiān)牢的瞬間灯荧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工盐杂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逗载,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓链烈,卻偏偏與公主長(zhǎng)得像厉斟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子强衡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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