自制前端框架Day15 寫寫scope放松一下

寫在前面

之前寫博客是為了記錄自己的開發過程,沒指望有人看,所以寫的非常凌亂。沒想到的是在這些日子里,竟然會有其他人看我的博客并給我評論留言,我感到受寵若驚。所以我要求自己寫博客要寫的更容易讓別人看懂,把自己的所想所得分享出去,如果有人看了我的博客,我希望的文字能對得起別人花費的時間。

前端框架是什么

其實前端框架做的事情都一樣,無非是解決了如何用數據來渲染頁面的問題:在我看來,無論react還是vue還是angular,其實都是controller+view。
拿我最熟悉的angular來說,數據與頁面的綁定就是scope與compile的綁定。
之前寫表達式解析部分寫的頭暈,決定換換腦子,寫寫scope。

scope的本質

scope其實就是一個普通對象,只不過上面封裝了很多方法而已。

scope的數據監測機制

在angular的scope中,有兩個部分是數據監測的關鍵,$watch和$digest。我目前不清楚vue的數據變化監測是什么機制,但是angular的是臟值監測,本質是給scope對象上增加watcher。

watcher是什么

watcher監控scope的某一個屬性,并且當屬性變化的時候執行回調函數。

$digest()是什么

digest負責把每一個watcher都檢查并且運行一次。

今天先決定寫這個部分。

在scope的對象上有很多watcher,所以要有一個地方存放這些watcher。

function Scope(){
    this.$$watchers=[]
}

scope有$watch函數,這個函數接受兩個參數,第一個參數用于指定該watcher監聽的屬性,第二個是屬性變化的時候執行的回調函數。

Scope.prototype.$watch=function(watchFn,listenFn){
    var watcher = {
        watchFn:watchFn,
        listenFn:listenFn
    }
    this.$$watchers.push(watcher);
}

$digest是用來執行所有watcher的listen方法,也很簡單,遍歷一下,執行就可以

Scope.prototype.$digest=function(){
    for(var i=0;i<this.$$watchers.length;i++){
        this.$$watchers[i].listenFn();
    }
}

寫一個測試案例試試看:

describe('scope', function() {
    var scope;
    beforeEach(function(){
        scope=new Scope()
    })
    it('scope可以賦值', function() {
        scope.name='wangji'
        expect(scope.name).toBe('wangji');
    });
     it('scope的watch和digest方法執行正常', function() {
        var watchFn=function(){
            return 'name'
        }
        var listenFn=jasmine.createSpy();
        scope.$watch(watchFn,listenFn);
        scope.$digest();
        expect(listenFn).toHaveBeenCalled();
    });
});
順利執行

watcher的實現就是典型的一種觀察者模式

watcher有兩個方法,一個是watchFn,一個是listenFn。watchFn用于獲取scope上某一個屬性的值:

watchFn=function(scope){
    return scope.id//這個watcher用于監聽scope.id的值
}

listenFn是一個回調函數,這樣一來就是很典型的觀察者模式:一個方法用來監聽某個對象的值,另一個方法是當監聽到相應動作時候執行。

臟值檢測的實現

每一個watcher既然能獲取scope上的一個屬性值,那么應該也可以保存上次的值。然后運行一次digest,把每一個watcher跑一次,如果這次拿到的值和上次保存的值不同,說明值是臟的,就可以運行listenFn回調函數,典型的觀察者模式。

function Scope(){
    this.$$watchers=[]
}
Scope.prototype.$watch=function(watchFn,listenFn){
    var watcher = {
        watchFn:watchFn,
        listenFn:listenFn,
        last:''
    }
    this.$$watchers.push(watcher);
}
Scope.prototype.$digest=function(){
    var self = this;
    var oldValue,newValue;
    for(var i=0;i<this.$$watchers.length;i++){
        oldValue = this.$$watchers[i].last;
        newValue = this.$$watchers[i].watchFn(self)
        if(oldValue!=newValue){
            this.$$watchers[i].last = newValue;
            this.$$watchers[i].listenFn();
        }
    }
}

執行以下案例看看效果

    it('臟值檢測',function() {
        scope.id=2;
        var watchFn=function(scope){
            return scope.id;
        }
        var listenFn=function(){
              console.log('listen!')
        }
        scope.$watch(watchFn,listenFn);
        scope.$digest();
    })

運行$digest方法時,期望中是這樣的:
watcher的last值最初是空的,調用watchFn后拿到id屬性的值,是2,進行對比,不相等,然后執行了listenFn,打印出listen!這句話。同時,watcher的last被設置為新值,也就是2.
來運行一下試試:

舊值和新值確實不相等
last已經變成最新值
打印出了語句,說明listenFn執行了

沒問題!那么再運行一次$digest的話,應該不會再打印listen了,因為經過第一次digest以后,watcher的last屬性被賦值為最新值,所以值不再臟了,也就不再運行listenFn了。

執行兩次digest,只運行了一次listenFn

完成,這就是臟值檢測的核心。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容