【一起讀】深入淺出Vue.js——Object的變化偵測

2.1 變化偵測是干什么的昂芜?

vue在渲染頁面時(shí)莹规,會(huì)根據(jù)數(shù)據(jù)的變化不停的進(jìn)行狀態(tài)的更替,也需要不停的對頁面進(jìn)行渲染泌神,變化偵測就是用來監(jiān)測狀態(tài)的變化良漱。

2.1.1 三大框架是如何監(jiān)測狀態(tài)的舞虱?

Angular&&React:當(dāng)狀態(tài)發(fā)生變化時(shí),發(fā)送信號(hào)告訴框架母市,框架接到信號(hào)后矾兜,通過暴力比對找到需要重新渲染的DOM節(jié)點(diǎn)。Angular中使用臟檢查患久,React中使用虛擬DOM椅寺。
Vue:狀態(tài)變化時(shí),vue立刻知道是誰發(fā)生了變化蒋失,(數(shù)據(jù)變化時(shí)主動(dòng)推送給框架)從而進(jìn)行dom 的更新返帕。

2.2 如何追蹤變化?

(本節(jié)暫只探討如何追蹤一個(gè)對象的變化)

JS中有兩種方式可以追蹤到變化:Object.defineProperty和ES6的Proxy篙挽。

vue中使用了Object.defineProperty來追蹤一個(gè)對象的變化:定義一個(gè)響應(yīng)式數(shù)據(jù)荆萤,每當(dāng)從data的key中讀取數(shù)據(jù)時(shí),get函數(shù)被觸發(fā)铣卡;每當(dāng)往data的key中設(shè)置數(shù)據(jù)是链韭,set函數(shù)被觸發(fā)。具體原理是:

function defineReactive(data,key,val){
  Object.defineProperty(data,key,{
    enumerable:true,
    configurable:true,
    get:function(){
      return val
    },
    set:function(newVal){
      if(val===newVal){
        return
      }
      val=newVal
    }
  })
}

2.3 依賴是什么算行?

vue的變化偵測具體是怎么發(fā)生的呢梧油?具體的說,一個(gè)狀態(tài)綁定著多個(gè)依賴州邢,每個(gè)依賴代表著一個(gè)具體的DOM節(jié)點(diǎn)儡陨,當(dāng)這個(gè)狀態(tài)發(fā)生變化時(shí),通過向這個(gè)狀態(tài)的所有依賴發(fā)送通知量淌,讓他們來更新DOM骗村。

那么依賴是什么呢?依賴就是當(dāng)屬性發(fā)生變化時(shí)呀枢,我們要通知的“用到數(shù)據(jù)的地方”胚股。

真實(shí)情況是,使用這個(gè)數(shù)據(jù)的地方有很多裙秋,類型也不一樣(有可能是一個(gè)模板琅拌,也有可能是用戶寫的一個(gè)watch),為了方便處理摘刑,我們需要抽象出一個(gè)能集中處理這些問題的類,然后我們在收集依賴階段只收集這個(gè)封裝好的類的實(shí)例枷恕,通知也只通知它一個(gè),讓它負(fù)責(zé)通知其他地方灾而,這里的“它”就被稱為“依賴”——Watcher旁趟。

2.4 如何收集依賴轻庆?

收集依賴是什么意思余爆?收集依賴就是把用到某一數(shù)據(jù)的地方收集起來的過程蛾方,等數(shù)據(jù)的屬性發(fā)生變化時(shí)上陕,把之前收集的依賴循環(huán)觸發(fā)一遍就好了释簿。
具體的說庶溶,在getter中收集依賴偏螺,在setter中觸發(fā)依賴。

2.5 依賴收集在哪里酿联?

最簡單的思路是:收集的依賴存在一個(gè)數(shù)組里贞让,在getter中將數(shù)據(jù)都push入這個(gè)數(shù)組喳张,然后在setter中循環(huán)這個(gè)數(shù)組來觸發(fā)所有依賴蹲姐。記這個(gè)數(shù)據(jù)為dep柴墩,那么:

function defineReactive(data,key,val){
  let dep=[]  //新增
  Object.defineProperty(data,key,{
    enumerable:true,
    configurable:true,
    get:function(){
      dep.push(window.target)  //新增
      return val
    },
    set:function(newVal){
      if(val===newVal){
        return
      }
    //新增
    for(let i=0;i<dep.length,i++){
      dep[i](val,newVal)
      }
      val=newVal
    }
  })
}

這樣的代碼耦合度過高江咳,現(xiàn)在我們把dep封裝成一個(gè)類:

export default class Dep(
      constructor(){
        this.subs=[]
      }
      addSub(sub){
        this.subs.push(sub)
      }
      removeSub(sub){
        remove(this.subs, sub)
      }
      depend(){
        if(window.target){
          this.addSub(window.target)
        }
      }
      notify(){
        const subs=this.subs.slice()
        for(let i=0;i<subs.length;i++){
          subs[i].update()
        }
      }
    )
    function remove(arr, item){
      if(arr.length){
        const index=arr.indexOf(item)
        if(index > -1){
          return arr.splice(index, 1)
        }
      }
    },
    function defineReactive(data,key,val){
      let dep=new Dep()  //新增
      Object.defineProperty(data,key,{
         enumerable:true,
         configurable:true,
         get:function(){
           dep.depend()  //新增
           return val
         },
         set:function(newVal){
           if(val===newVal){
              return
            }
           val=newVal
           dep.notify()//新增
         }
     })
 }

2.6 重新介紹watcher 是什么歼指?

白話:Watcher是一個(gè)中介的角色踩身,數(shù)據(jù)發(fā)生變化時(shí)通知它挟阻,然后它再通知其他地方附鸽。

Watcher的原理是把自己設(shè)置到全局唯一的指定位置(例如window.target)瞒瘸,然后讀取數(shù)據(jù)——>觸發(fā)數(shù)據(jù)的getter,在getter中就從全局唯一的那個(gè)位置讀取當(dāng)前正在讀取數(shù)據(jù)的Watcher省撑,并把這個(gè)Watcher收集到Dep丁侄,這樣朝巫,Watcher就可以訂閱任意一個(gè)數(shù)據(jù)的變化劈猿。

Watcher的經(jīng)典使用方法是:

 // keypath
    vm.$watch('a.b.c',function(newVal,oldVal){
      // 做點(diǎn)什么
    })

當(dāng)data.a.b.c發(fā)生變化時(shí)揪荣,觸發(fā)第二個(gè)參數(shù)中的函數(shù)

如何實(shí)現(xiàn)這個(gè)功能呢:把這個(gè)watcher實(shí)例添加到data.a.b.c屬性的Dep中,當(dāng)data.a.b.c的值發(fā)生變化時(shí)佛舱,通知Watcher,然后Watcher執(zhí)行參數(shù)中的這個(gè)回調(diào)函數(shù)订歪。代碼如下:

export default class Watcher{
      constructor (vm,expOrFn,cb){
        this.vm=vm
        // 執(zhí)行this.getter()刷晋,就可以讀取data.a.b.c的內(nèi)容
        this.getter=parsePath(expOrFn)
        this.cb=cb
        this.value=this.get()
      }
      get(){
        window.target=this
        let value=this.getter.call(this.vm,this.vm)
        window.target=underfined
        return value
      }
      update(){
        const oldValue=this.value
        this.value=this.get()
        this.cb.call(this.vm,this.value,oldValue)
      }
    }

代碼解析

  • 在get()中眼虱,this指向當(dāng)前watcher實(shí)例捏悬,觸發(fā)了getter就會(huì)觸發(fā)收集依賴的機(jī)制润梯,即從window.target中讀取一個(gè)依賴并且添加到Dep中。
  • 依賴注入到Dep之后抒和,每當(dāng)data.a.b.c的值發(fā)生變化時(shí)摧莽,就會(huì)讓依賴列表中所有的依賴循環(huán)觸發(fā)update方法顿痪,而update方法會(huì)執(zhí)行參數(shù)中的回調(diào)函數(shù)將value和oldValue傳到參數(shù)中蚁袭。

總結(jié):不管是用戶執(zhí)行vm.$watch('a.b.c',(value,oldValue)=>{})揩悄,還是模板中用到的data,都是通過Watcher來通知自己是否需要發(fā)生變化亏娜。

補(bǔ)充介紹1:parsePath是怎么讀取一個(gè)字符串的keypath的维贺?

//解析簡單路徑
    const bailRE=/[^\w.$]/
    export function parsePath(path){
      if(bailRE.test(path)){
        return
      }
      const segments=path.split('.')
      return function(obj){
        for(let i=0;i<segments.length;i++){
          if(!obj)return
          obj=obj[segments[i]]
        }
        return obj
      }
    }

代碼解析

  • 使用.分割keypath為一個(gè)數(shù)組溯泣,然后循環(huán)數(shù)組一層一層去讀數(shù)據(jù),最后拿到的obj就是keypath中想要讀的數(shù)據(jù)客给。

補(bǔ)充介紹2:keypath是什么?

  • keypath(鍵路徑)是一個(gè)由 . 作為分隔符的鍵組成的字符串译仗,用于支撐一個(gè)連接在一起的對象性質(zhì)序列官觅。第一個(gè)鍵的性質(zhì)由先前的性質(zhì)決定,接下來每個(gè)鍵的值也是相對于其前面的性質(zhì)咱圆。
  • keypath用于鍵值額觀察功氨,當(dāng)另一個(gè)對象的屬性發(fā)生變化時(shí)捷凄,可以直接通知對象跺涤。
  • 通過keypath找到這個(gè)屬性的值,然后去檢查這個(gè)值是否有變化航唆,從而得知該對象是否發(fā)生了變化糯钙。

關(guān)于keypath的詳情介紹請點(diǎn)擊:http://www.reibang.com/p/e008f73a35ba

2.7 遞歸偵測所有key

前面介紹的代碼只能偵測數(shù)據(jù)中的某一個(gè)屬性换可,可以封裝一個(gè)肤晓?observer類來偵測數(shù)據(jù)中的所有屬性米碰。

    export class Observer(){
      constructor (value){
        this.value=value
        if(!Array.isArray(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(data,key,val){
      // 新增虐译,遞歸子屬性
      let dep=new Dep()  //新增
      Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
          dep.depend()  //新增
          return val
        },
        set:function(newVal){
          if(val===newVal){
              return
            }
          val=newVal
          dep.notify()//新增
        }
    })
}

代碼解析:
1 Objecter類將一個(gè)正常的object轉(zhuǎn)換成被偵測的object漆诽,即將一個(gè)數(shù)據(jù)內(nèi)的所有屬性都轉(zhuǎn)換成getter/setter的形式厢拭,然后去追蹤它們的變化供鸠。
2 判斷數(shù)據(jù)的類型陨闹,只有Object類型的數(shù)據(jù)才會(huì)調(diào)用walk將每一個(gè)屬性轉(zhuǎn)換成getter/setter的形式來偵測變化寨闹。
3 早defineReactive中新增new Observer(val)來遞歸子屬性君账,這樣就完成了偵測所有屬性的功能。當(dāng)data中的屬性發(fā)生變化時(shí)帖蔓,與這個(gè)屬性相對應(yīng)的依賴就會(huì)接收到通知塑娇。

2.8 關(guān)于Object的問題

Vue.js通過Object.defineProoerty將對象的key轉(zhuǎn)換成getter/setter的形式來追蹤變化埋酬,但是getter/setter只能追蹤一個(gè)數(shù)據(jù)是否是修改写妥,不能追蹤到數(shù)據(jù)是新增還是刪除珍特,為了解決這個(gè)問題魔吐,Vue.js提供了兩個(gè)API:vm.set和vm.delete扎筒,后文會(huì)介紹莱找。
像對象中新增屬性:

var vm=new Vue(){
      el:'#el',
      template:'#demo-template',
      methods:{
        action(){
          //新增屬性值
          this.obj.name='xiaoming'
        }
      },
      data:{
        obj:{}
      }
    }

在對象中刪除屬性:

var vm=new Vue(){
      el:'#el',
      template:'#demo-template',
      methods:{
        action(){
          //刪除一個(gè)屬性
          delete this.obj.name
        }
      },
      data:{
        obj:{
          name:'xiaoming'
        }
      }
    }
image.png

聲明:本文參考自:劉博文-深入淺出Vue.j

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嗜桌,隨后出現(xiàn)的幾起案子奥溺,更是在濱河造成了極大的恐慌,老刑警劉巖骨宠,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浮定,死亡現(xiàn)場離奇詭異,居然都是意外死亡层亿,警方通過查閱死者的電腦和手機(jī)桦卒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門闸盔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來针贬,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我育灸,道長砸喻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任惠爽,我火速辦了婚禮较性,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好健芭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布谴麦。 她就那樣靜靜地躺著面哼,像睡著了一般河胎。 火紅的嫁衣襯著肌膚如雪吭历。 梳的紋絲不亂的頭發(fā)上朗若,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼羡滑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的削葱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼编检,長吁一口氣:“原來是場噩夢啊……” “哼琅捏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起据忘,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎晶伦,沒想到半個(gè)月后婚陪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寄疏。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡罚渐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驯妄,到底是詐尸還是另有隱情荷并,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布青扔,位于F島的核電站源织,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏微猖。R本人自食惡果不足惜谈息,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凛剥。 院中可真熱鬧侠仇,春花似錦、人聲如沸犁珠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽犁享。三九已至余素,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炊昆,已是汗流浹背桨吊。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工威根, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人视乐。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓医窿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炊林。 傳聞我的和親對象是個(gè)殘疾皇子姥卢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

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

  • 深入淺出 - vue變化偵測原理 關(guān)于vue的內(nèi)部原理其實(shí)有很多個(gè)重要的部分,變化偵測渣聚,模板編譯独榴,virtualD...
    如煙灬閱讀 257評論 0 1
  • 3.1 如何追蹤變化 Object通過觸發(fā)getter/setter來實(shí)現(xiàn)變化偵測,在Array中,使用push等...
    小妍妍說閱讀 529評論 0 1
  • 變化偵測 偵測狀態(tài)變化奕枝,重新渲染頁面棺榔。 拉(通知狀態(tài)改變,然后暴力比對哪些節(jié)點(diǎn)需要重新渲染): Angular臟檢...
    joyce594閱讀 396評論 0 0
  • 1.1 什么是變化偵測 vue.js會(huì)自動(dòng)通過狀態(tài)生成DOM隘道,并將其輸出到頁面顯示症歇,這個(gè)過程叫渲染。vue.js的...
    李友勝閱讀 478評論 0 3
  • 前言 Vue 最獨(dú)特的特性之一谭梗,是其非侵入性的響應(yīng)式系統(tǒng)忘晤。數(shù)據(jù)模型僅僅是普通的 JavaScript 對象。而當(dāng)你...
    浪里行舟閱讀 1,972評論 0 16