angularJS 核心理念之 雙向數據綁定

雙向數據綁定是angularJS的核心理念之一。

單向數據綁定:


單向數據即是將用於生成界面的模板與從服務器取得的數據結合,生成用於顯示的html標籤。

比如El表達式中常見 ${變量名}以及{{}} ,它只提供從數據源到視圖的單方向的數據展示。 

單向數據綁定的缺點是界面一旦生成,就不能更改,如果數據有更改,只能再來一遍,替換掉原來的html。

再看雙向數據綁定:


雙向數據綁定中,視圖和數據是對應的,一方發生變化, 另一方立刻跟着變化。

雙向數據綁定最大的好處就是當數據改變後,不需要在代碼中手動更新視圖,簡化開發,增加代碼內聚性,代碼邏輯、可讀性更強。

缺點是雙向數據綁定完全契合的場景比較少,當綁定的數據層次深、數據量大時,實現雙向數據綁定會有一定性能開銷。

angular雙向數據綁定的性能問題與angular實現雙向數據綁定的機制有關。

angular雙向數據綁定的實現

        angular實現雙向數據綁定使用了髒檢查機制:

         髒檢查機制:Angular將雙向綁定轉換爲一堆watch表達式,然後遞歸這些表達式檢查是否發生過變化,如果變了則執行相應的watcher函數(指view上的指令,如ng-bind,ng-show等或是{{}})。等到model中的值不再發生變化,也就不會再有watcher被觸發,一個完整的循環就完成了。
        髒檢查機制的觸發:Angular中在view上聲明的事件指令,如:ng-click、ng-change等,會將瀏覽器的事件轉發給$scope上相應的model的響應函數。等待相應函數改變model,緊接着觸發髒檢查機制刷新view。

        watch表達式:可以是一個函數、可以是$scope上的一個屬性名,也可以是一個字符串形式的表達式。$watch函數所監聽的對象叫做watch表達式。

        watcher函數:指在view上的指令(ngBind,ngShow、ngHide等)以及{{}}表達式,他們所註冊的函數。每一個watcher對象都包括:監聽函數,上次變化的值,獲取監聽表達式的方法以及監聽表達式,最後還包括是否需要使用深度對比(angular.equals())。

$watch(watchFn,watchAction,deepWatch)

        watchFu:angular表達式或函數的字符串;

        watchAction(newValue,oldValue,scope):watchFu發生改變會調用;

        deepWatch:true/false,是否深度檢查

        $watch會返回一個函數,該函數可以註銷watcher。

var watcher = $scope.$watch('data',function(){},true);
//註銷
watcher();

        手動觸發髒檢查:

$scope.$digest();
        或者:
$scope.$apply() //實際上也是調用$digest()

        在$digest循環過程中,angular會遍歷每一個watcher,詢問它是否有屬性和值的變化(變髒)。

        如果在$digest循環中,watcher中的回調修改了屬性和值怎麼辦?

        實際上,$digest循環會循環觸發,直到所有的值都不在改變,所以$digest循環至少會執行兩次,但最多10次,之後則會報錯。當$digest循環結束,修改DOM。

        仿寫angular的雙向數據綁定:

class Scope{
    construct(){
        this.nodes =[];         //綁定的節點
        this.watchers = [];     //model數據的觀察者
    }
    /**
     * 更新view中綁定節點的值
     */
    update(newValue){
        let {nodes} = this;
        const INPUT_NODE = ['INPUT','TEXTAREA'];        //節點是否可輸入
        for(let i=0,node;node=nodes[i++];){
            if(INPUT_NODE.includes(node.nodeName)){
                if(node.value !== newValue){
                    node.value = newValue;
                }
            }else{
                node.textContent = newValue;
            }
        }
    }

    /**
     * 爲model中的值新增watcher
     * @param statement
     * @param listener
     */
    watch(statement,listener = function () {}){
        this.watchers.push({
            statement,
            listener
        })
    }

    /**
     * 節點與數據綁定
     * @param node
     */
    bindNode(node){
        let key = node.getAttribute('ng-model');
        if(!key){
            return;
        }
        this.nodes.push(node);
        //綁定新節點直接更新一次數據,使各節點數據一致
        this.update(this[key]);
        this.watch(()=>{
            return this[key];
        },(newValue,oldValue)=>{
            this.update(newValue);
        })
    }

    /**
     * 觸發髒檢查
     */
    $digest(){
        let {watchers} = this;
        //如果髒檢查中更新了數據,需要再循環一次
        let dirty = false;
        do {
            dirty = false;
            for (let i = 0, watcher; watcher = watchers[i++];) {
                let newValue = watcher.statement();
                if (watcher.last !== newValue) {
                    dirty = true;
                    watcher.listener(newValue, watcher.last);
                    watcher.last = newValue;
                }
            }
        }while(dirty);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章