寫在前面
之前寫博客是為了記錄自己的開發過程,沒指望有人看,所以寫的非常凌亂。沒想到的是在這些日子里,竟然會有其他人看我的博客并給我評論留言,我感到受寵若驚。所以我要求自己寫博客要寫的更容易讓別人看懂,把自己的所想所得分享出去,如果有人看了我的博客,我希望的文字能對得起別人花費的時間。
前端框架是什么
其實前端框架做的事情都一樣,無非是解決了如何用數據來渲染頁面的問題:在我看來,無論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.
來運行一下試試:
沒問題!那么再運行一次$digest的話,應該不會再打印listen了,因為經過第一次digest以后,watcher的last屬性被賦值為最新值,所以值不再臟了,也就不再運行listenFn了。
完成,這就是臟值檢測的核心。