嘗試實現vue中的MVVM--菜雞的小日子

引言

一直想要好好學一下關於框架原理的東西,奈何以前的課程任務太重,現在終於得空可以學習學習,跟着一個牛逼的博客做下去,相信自己能夠得到很大的提升的哈哈哈哈哈哈,不想看我的可以直接去他那裏seesee,傳送門

前期知識

先了解一下MVC和MVVM地工作原理:
MVC可謂是十分經典:
在這裏插入圖片描述

  • 視圖(View):用戶界面。
  • 控制器(Controller):業務邏輯
  • 模型(Model):數據保存
    各部分之間的通信方式如下:
  • View 傳送指令到 Controller
  • Controller 完成業務邏輯後,要求 Model 改變狀態
  • Model 將新的數據發送到 View,用戶得到反饋
  • 所有通信都是單向的。

MVVM現今也是十分的流行:
在這裏插入圖片描述

  1. 所有的通信都是雙向的
  2. view和model層不發生關係,所有的都通過ViewModel傳遞~~
  3. view層其實比較薄,而ViewModel比較厚,所有的邏輯代碼什麼的都在這裏面
  4. view和ViewModel採用的是雙向綁定,view的變動自動反映在viewModel中,反之亦然~

先嚐試一個小demo

在vue中其實比較核心的是Object.defineProperty(obj, prop, descriptor)這個API。
我們需要注意的是,使用的時候不能同時設置訪問器(getter和setter)和writeable或者value,否則會會報這樣的錯誤:
TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

step1:defineProperty

let callback={
    target: null
}

let defineReactive=function(object,key,value){
    let array=[]
    Object.defineProperty(object,key,{
        configurable: true,
        enumerable:true,
        get: function(){
            if(callback.target){
                array.push(callback.target)
            }
            return value
        },
        set: function(newValue){
            if(newValue!=value){
                array.forEach(fun => {
                    fun(newValue,value)
                });
            }   
            value=newValue;
        }
    })
}


let object={};
defineReactive(object,'test','test');
callback.target=function(newValue,oldValue){
    console.log("我被添加進去了,新的值是",newValue);
}
object.test;
callback.target=null;
object.test='test2';
callback.target=function(newValue,oldValue){
    console.log("添加第二個函數,新的value",newValue);
}
object.test
callback.target=null;
object.test='test3';

當我們取值的時候,函數自動幫我們添加針對當前值的依賴,當我們的值發生變化的時候,會自動處理這些依賴,比如我們的dom變化的時候。
這其實也是vue的核心代碼,只是它的實現要遠比我們的複雜許多!

step2 Dep

上一步我們實現了VUE的依賴收集和觸發,但是我們只是將它放在了一個內置的數組裏面,雖然容易理解但是十分不好維護,爲了更加好地維護,需要事先一個維護依賴的類!
類下屬性:target 函數,用於存放需要添加的依賴
實例下屬性
subs/array:存放依賴
addsub/function: 添加依賴
removesub/function:刪除依賴
notify/function:執行依賴

let Dep=function(){
    //實例屬性
    this.subs=[]
    //實例方法
    this.addSub=function(sub){
        this.subs.push(sub);
    }
    this.removeSub=function(sub){
        const index=this.subs.indexOf(sub);
        if(index>-1){
            this.sub.splice(index,1);
        }
    }
    this.notify=function(newValue,oldValue){
        this.subs.forEach(func => {
            func(newValue,oldValue)
        });
    }
}
//類屬性
Dep.target=null;

let defineReactive=function(object,key,value){
    let dep=new Dep();
    Object.defineProperty(object,key,{
        configurable:true,
        enumerable:true,
        get: function(){
            if(Dep.target){
                dep.addSub(Dep.target)
            }
            return value
        },
        set:function(newValue){
            if(newValue!=value){
                dep.notify(newValue,value);
            }
            value=newValue;
        }
    })
}

let object={};
defineReactive(object,'test','test');
Dep.target=function(newValue,oldValue){
    console.log('我被添加進去了,新的值',newValue);
}
object.test;
Dep.target=null;
object.test='test2';
Dep.target=function(newValue,oldValue){
    console.log('添加第二個函數,新的值是',newValue);
}
object.test;
Dep.target=null
object.test='test3';
  1. dep類將監聽屬性和處理依賴進行了一定的解耦,但是解耦不是很完全,在我們觸發依賴的時候還是得傳遞新舊值。
  2. 刪除依賴肯定在外部環境中,但是我們的dep實例是在defineReactive作用域裏面,外部沒有辦法直接進行調用!
    vue中的Watcher類處理了以上兩個問題,後面說!
    剛剛的Dep類可以在es6的語法下:
class Dep{
     constructor(){
         this.subs=[];
     }
     addSub(sub){
         this.subs.push(sub);
     }
     removeSub(sub){
         const index=this.subs.indexOf(sub);
         if(index>-1){
             this.subs.splice(index,1);
         }
     }
     notify(){
         this.subs.forEach(func=>func(newValue,oldValue))
     }
 }
//類屬性
Dep.target=null;

step3 Watcher

  1. 解耦不完全,需要傳遞參數
  2. 沒有地方可以移除依賴
//其中wacher是存入subs數組裏面的一個個元素
let Watcher= function(object,key,callback){
    this.obj=object;
    this.getter=key;
    this.cb=callback;
    this.dep=null;
    this.value=undefined;

    this.get=function(){
        Dep.target=this;
        let value=this.obj[this.getter];//有個object、get操作
        Dep.target=null;
        return value;
    }

    this.update=function(){
        const value = this.obj[this.getter];
        const oldValue=this.value;
        this.value=value;
        this.cb.call(this.obj,value,oldValue);
    }
    this.addDep=function(dep){
        this.dep=dep;
    }
    this.value=this.get();
}


let Dep=function(){
    this.subs=[];
    this.addSub=function(sub){
        this.subs.push(sub);
    }
    this.removeSub=function(sub){
        const index=this.subs.indexOf(sub);
        if(index>-1){
            this.subs.splice(index,1)
        }
    }
    this.notify=function(){
        this.subs.forEach(watcher => {
            watcher.update();
        });
    }
}

Dep.target=null;

let defineReactive=function(object,key,value){
    let dep=new Dep();
    Object.defineProperty(object,key,{
        configurable:true,
        enumerable:true,
        get:function(){
            if(Dep.target){
                dep.addSub(Dep.target)
                // 添加 watcher 對 dep 的引用
                Dep.target.addDep(dep)
            }
            return value;
        },
        set: function(newValue){
            if(newValue!=value){
                value=newValue;
                dep.notify();
            }
        }
    })
}

let object = {}
defineReactive(object, 'test', 'test') 

let watcher = new Watcher(object, 'test', function(newValue, oldValue){
    console.log('作爲 watcher 添加的第一個函數,很自豪。新值:' + newValue)
})
object.test = 'test2'
// 作爲 watcher 添加的第一個函數,很自豪。新值:test2

let watcher2 = new Watcher(object, 'test', function(newValue, oldValue){
    console.log('作爲 watcher 添加的第二個函數,也很自豪。新值:' + newValue)
})
object.test = 'test3'
// 作爲 watcher 添加的第一個函數,很自豪。新值:test3
// 作爲 watcher 添加的第二個函數,也很自豪。新值:test3

// 接着我們來試一下刪除依賴,把 watcher2 給刪除
watcher2.dep.removeSub(watcher2)
object.test = 'test4'
// 作爲 watcher 添加的第一個函數,很自豪。新值:test4

上面的代碼實現了一個Watcher,實例裏面保存了:需要監聽的對象(object)、取值方法(key)、對應的回調(callback)、需要監聽的值(value)、以及取值函數(get)和出觸發函數(update),這樣我們就把依賴相關的所有內容都保存在了這個實例裏面。
爲了保存對Dep的引用,在Watcher中設置了dep,用於保存這個監聽被那個Dep引用了。
編寫代碼的時候我們不用特地的觸發get來添加依賴因爲我們將之前的get值的代碼放在了watcher裏面:
修改前:

Dep.target = function(newValue, oldValue){console.log('我被添加進去了,新的值是:' + newValue)}  
object.test
Dep.target = null  

修改後:

this.get = function(){
    Dep.target = this
    let vaule = this.obj[this.getter]
    Dep.target = null
    return value
}

this.value = this.get()

最終,通過以上的代碼我們解決了之前的問題。
問題:這時候呢我們又出現了一個問題:那就當前的Watcher還是需要優化的,比如如果被多個Dep引用的話,就得存一個數組嚶嚶嚶

setp4 優化Watcher

目前爲止我們做了三個東西

  1. 通過defineReactive函數,實現了對數據取值和設置的監聽!!Object.defineProperty
  2. 通過Dep類,實現了依賴的管理
  3. 通過Watcher類,實現瞭解耦等等。

問題:

  1. 一個watcher對應多個屬性。
  2. 多個Dep依賴於同一個Watcher的情況。
//其中wacher是存入subs數組裏面的一個個元素
let Watcher= function(object,getter,callback){
    this.obj=object;
    this.getter=getter;
    this.cb=callback;
    this.deps=[];
    this.value=undefined;

    this.get=function(){
        Dep.target=this;
        let value=this.getter.call(object)//有個object、get操作
        Dep.target=null;
        return value;
    }

    this.update=function(){
        const value=this.getter.call(object);
        const oldValue=this.value;
        this.value=value;
        this.cb.call(this.obj,value,oldValue);
    }
    this.addDep=function(dep){
        this.deps.push(dep);
    }
    //新調節的取消依賴的方法
    this.teardown=function(){
        let i=this.deps.length;
        while(i--){
            this.deps[i].removeSub(this)
        }
        this.deps=[];
    }
    this.value=this.get();
}


let Dep=function(){
    this.subs=[];
    this.addSub=function(sub){
        this.subs.push(sub);
    }
    this.removeSub=function(sub){
        const index=this.subs.indexOf(sub);
        if(index>-1){
            this.subs.splice(index,1)
        }
    }
    this.notify=function(){
        this.subs.forEach(watcher => {
            watcher.update();
        });
    }
}

Dep.target=null;

let defineReactive=function(object,key,value){
    let dep=new Dep();
    Object.defineProperty(object,key,{
        configurable:true,
        enumerable:true,
        get:function(){
            if(Dep.target){
                dep.addSub(Dep.target)
                // 添加 watcher 對 dep 的引用
                Dep.target.addDep(dep)
            }
            return value;
        },
        set: function(newValue){
            if(newValue!=value){
                value=newValue;
                dep.notify();
            }
        }
    })
}

let object={};
defineReactive(object,'num1',2);
defineReactive(object,'num2',4);
let watcher=new Watcher(object,function(){
    return this.num1+this.num2
},function(newValue,oldValue){
    console.log(`這是一個監聽函數,${object.num1} + ${object.num2} = ${newValue}`)
});
let watcher2=new Watcher(object,function(){
    return  this.num1*this.num2
},function(newValue,oldValue){
    console.log(`這是一個監聽函數,${object.num1} * ${object.num2} = ${newValue}`);
});
object.num1=4;
object.num2=11;

//測試取消
watcher2.teardown();
object.num1=5;
object.num2=12;

其實最終優化就是兩點:

  1. 傳入Watcher的不再是個key值,而是一個函數,這個函數裏面呢,我們觸發了多個屬性的get
  2. 需要保存多個dep的話,將它用一個數組保存起來就可以了。
  3. 當然,爲了方便取消這個watcher,我們需要的是添加函數爲了取消所有dep對watcher的依賴。

自己小結一下
可能有點亂,之前我甚至沒有把watcher和dep之間的關係弄懂,於是我花了個小圖圖,其實就是要實現一個一對多的關係,但是同時爲了watcher能夠保存對dep的引用,我們需要將依賴於此的dep保存到watcher裏面(作用域啥的知識),以便於 引用。
在這裏插入圖片描述

step5 Observe

我們已經有了:

  1. defineReactive控制了對象屬性,變爲可監聽結構
  2. Dep收集管理依賴
  3. Watcher一個抽象的依賴

defineReactiveDep改造了對象下面的某一個屬性,將一個目標變成了觀察者模式中的目標,目標發生變化的時候會調度觀察者;
Watcher就是一個觀察者,會註冊到目標裏面。
需要優化的地方:
4. 如果想要對象的屬性都得以響應,必須對對象下的所有屬性進行遍歷,依次調用 defineReactive不是很方便。
5. 代碼都在一個文件裏面十分不便與管理
最後實現看 https://github.com/CoCoManYY/MyWheel/tree/master/myMVVM/demo/step5
其中的配置採坑筆記

step6 Array

之前的幾個step實現了對象屬性的監聽,但是有關於數組的行爲一直沒有進行處理。

  • 調用方法:arr.splice(1,2,"something","something2")
  • 直接賦值:arr[1]='something'
    總結對原數組造成影響的方法:push、pop、shift、unshift、splice、sort、reverse。數組長度發生變化,數組內元素順序發生變化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章