Vue響應式原理底層代碼模擬實現


整體分析Vue的基本結構如下圖所示:(備注:完整代碼github地址https://github.com/1512955040/MiniVue)


上圖中,為我們模擬最小vue的整體結構,首先創建一個vue類型,它負責把data中的成員注入到vue實例中,並且轉化成getter/setter,observer的作用是數據劫持,對data中的屬性進行數據監聽,如果數據發生變化會獲取到最新的值,並通知dep。Compiler的作用是解析每個元素中的指令和差值表達式並替換成相應的數據。Dep的作用是添加觀察者,當數據發生變化時通知所有的觀察者。Watcher內部有一個Update方法負責更新視圖,下面我們用代碼的方式一一進行實現。

1.Vue.js功能:

1-1負責接收初始化的參數(選項)

1-2負責把data中的屬性注入到vue實例,轉化成getter/setter

1-3負責調用observer監聽data中所有屬性的變化

1-4負責調用Compiler解析指令/差值表達式

類圖結構如下:

如上圖所示:vue類中有三個屬性,分別是$options,$el,$data,這三個屬性記錄構造函數中傳過來的參數。_proxyData為vue類中的方法

所以以_開頭的成員就是私有成員,這個方法的功能是把data中的屬性轉化為getter和setter注入到vue實例中。

class Vue{
    constructor(options) {
        //1.通過屬性保存選項中的數據
        this.$options = options || {}
        this.$data = options.data || {}
        this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
        //2.把data中的成員轉化為getter和setter,注入到vue實例中
        this._proxyData(this.$data)
        //3.調用observer對象,監聽數據的變化
        new Observer(this.$data)
        //4.調用compiler對象,解析指令和差值表達式
        new Compiler(this)
    }
    //把Vue的屬性轉化為getter和setter,注入到Vue實例中
    _proxyData(data){
        //遍歷data中所有屬性
        Object.keys(data).forEach(key=>{
          //把data的屬性注入到vue實例全局中
          Object.defineProperty(this,key,{
              enumerable:true,
              configurable:true,
              get(){
                return data[key]
              },
              set(newValue){
                if(newValue===data[key]){
                    return 
                }
                data[key]=newValue
             }
        })    
      })
    }
}

2.Observer.js功能(數據劫持):

2-1 負責把data選項中的屬性轉化為響應式數據

2-2 data中的某個屬性也是對象,把該屬性轉化為響應式數據

2-3 數據變化發送通知

類圖結構如下:

如上圖所示:

walk方法的作用是遍歷data中的所有屬性,defineReactive是定義響應式數據,也就是通過調用defineReactive方法把屬性轉化為getter和setter。

class Observer{
    constructor(data) {
        this.walk(data)
    }
    //walk方法遍歷data中的所有屬性
    walk(data) {
        //1.判斷data是否對象
        if(!data || typeof data !=='object'){
            return 
        }
        //2.遍歷data對象的所有屬性
        Object.keys(data).forEach(key=>{
            this.defineReactive(data,key,data[key])
        })
    }
    //degineReactivce方法定義響應式數據 把屬性轉化為getter和setter
    defineReactive(obj,key,val) {
        let that=this
        // 負責收集依賴,並發送通知
        let dep=new Dep()
        //如果val傳入對象的話也給對象里面的屬性添加getter和setter方法
        this.walk(val)
        Object.defineProperty(obj,key,{
            enumerable:true,
            configurable:true,
            get(){
                // 收集依賴
                Dep.target && dep.addSub(Dep.target)
                return val
            },
            set(newValue){
                if(newValue==val){
                    return 
                }
                val=newValue
                //如果給屬性重新賦值成對象,給對象里面的屬性重新添加getter和setter方法
                //比如:歷史數據vm.msg="Hello World" 修改之后vm.msg={a:'Hwllo World'}
                //再次調用此方法給vm.msg.a重新添加getter和setter方法
                that.walk(newValue)
                //發送通知
                dep.notify()
            }
        })
    }
}

3.Compiler.js功能:

3-1 負責編譯模板,解析指令/差值表達式

3-2 負責頁面的首次渲染

3-3 當數據變化后重新渲染視圖

類圖結構如下:

如上圖所示:

el為構造函數傳過來的options.el,vm是vue的實例,下面都是vm的方法,對DOM進行操作。compile方法內部遍歷dom對象的所有節點,並且

判斷這些節點是文本節點,如果是文本節點解析差值表達式,如果是元素節點解析指令,isTextNode和isElementNode方法判斷是文本節點還

是元素節點。compileElement和compileText方法解析差值表達式和指令。isDirective這個方法判斷元素屬性是否是指令。

4.Dep.js功能:

4-1 收集依賴,添加觀察者(watcher)

4-2 通知所有觀察者

如上圖所示:

在vue的響應式機制中,使用觀察者模式來響應數據的變化,Dep的作用是收集依賴,在getter方法中收集依賴,在setter方法中通知依賴,每

一個響應式的屬性都會場景一個Dep對象,負責收集所有依賴於該屬性的地方,所有依賴於該屬性的位置都會創建一個watcher對象,所以

Dep就是收集於該屬性的watcher對象,使用setter方法去通知依賴,當屬性發生變化時調用nodify方法去發送通知,然后調用watcher對象

的update方法。

類的機構如下圖:

如上圖所示:

subs是一個數組,存儲dep中所有的watcher,addSub方法添加watcher,notify方法發布通知

class Dep{
    constructor() {
        //存儲所有的觀察者
        this.subs=[]
    }
    // 添加觀察者
    addSub(sub){
        if(sub && sub.update) {
            this.subs.push(sub)
        }
    }
    //發送通知
    notify(){
        this.subs.forEach(sub =>{
            sub.update()
        })
    }
}

 5.Watcher.js功能:

5-1 當數據變化觸發依賴,dep通知所有的Watcher實例更新視圖

5-2 自身實例化的時候往dep對象中添加自己

如上圖所示:

data中的每一個屬性都要創建一個Dep對象, 在收集依賴的時候把所有對象的watcher添加到dep對象的subs數組中,在setter對象中發送通

知,調用Dep對象的notify方法通知所有關聯的watcher對象更新視圖。

類圖結構如下:

如上圖所示:

update對象更新視圖,cb回調函數,指明如何更新視圖。在更新視圖的時候需要一個屬性key(data中的屬性名稱),oldvalue是key 對應的值。

class Watcher{
    constructor(vm,key,cb) {
        this.vm=vm
        //data中的屬性名稱
        this.key=key
        //回調函數負責更新視圖
        this.cb=cb
        //把watcher對象記錄到Dep類的靜態屬性target
        Dep.target =this
        //觸發get方法,在get方法中會調用addSub
        this.oldValue=vm[key]
        Dep.target=null
    }
    //當數據發生變化時更新視圖
    update(){
        let newValue=this.vm[this.key]
        if(this.oldValue === newValue){
            return
        }
        this.cb(newValue)
    }
}

下面通過這張圖作整體流程的總結:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM