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);
    }
}

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