MVVM双向数据绑定的个人理解

2019-04-23  本文已影响0人  revert

AngularJS 的脏检查机制

AngularJS的双向数据绑定采用的脏检查机制,所谓“脏检查”,就是检测到数据与之前不一致,有变化,这时候就认为数据是“脏”的,然后把不一致的数据变更为较新的值,直到没有新的数据变动。

通常UI事件、ajax请求后的数据处理 或者 timeout 延迟事件会触发这个脏检查这个过程的实现,主要是靠 $watch$digest 两个重要的函数。

$watch

每当有变量通过$scope对象和页面UI绑定,就会增加一个watch对象

watch = {
    name:'',      //当前的watch 对象 观测的数据名
    getNewValue:function($scope){ //得到新值
        ...
        return newValue;
        },
    listener:function(newValue,oldValue){  // 当数据发生改变时需要执行的操作
        ...
    }
}

$watch函数会收集所有的watch对象到$$watchList数组中,然后等待$digest遍历$$watchList中的listener,用代码表示更为直观

$scope.prototype.$watch = function(name,getNewValue,listener){
    var watch = {
       name:name,
       getNewValue : getNewValue,
       listener : listener
    };

    this.$$watchList.push(watch);
}

挂载到$scope原型上是为了每个Scope实例上存储这些函数。

$digest

$digest函数的作用就是在触发脏检查的时候,遍历之前push到$$watchList中的watch.listener,用代码解释就是这样:

$scope.prototype.$digest = function(){
    var list = this.$$watchList;
    for(var i = 0,l= list.length;i++){
        var watch = list[i];
        var newValue = watch.getNewValue(this);
        // 在第一次渲染界面,进行一个数据呈现.
        var oldValue = watch.last;
        if(newValue!=oldValue){
            watch.listener(newValue,oldValue);
        }
        watch.last = newValue;
    }
}

watch.last用于存储上一次的值,当newValue!=oldValue时,就认为数据上“脏”的,就会递归调用$digest,保证所有数据都一致。$digest至少会调用两次,才能确保数据是干净的,当然也不会无休止的递归下去,递归10次就会抛出异常了,这种情况说明业务数据过于复杂(或者代码特别渣渣),需要开发人员优化一下了。

Vue的数据劫持模式

vue的双向数据绑定,采用数据劫持模式,核心是Object.defineProperty,Vue3.0的核心要变成proxy了。

通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化

Object.defineProperty

对于vue({data:{}}),data对象上的所有属性,都会被Object.defineProperty劫持,增加对应的gettersetter,结合发布订阅模式,就可以对数据检测赋值,这里说明一下数据劫持部分,代码如下:


function observe(data){ 
    if(typeof data !== 'object'){//不是对象不进行数据劫持
        return
    }
    return new Observe(data);
}
 
//将model->vm.data
function Observe(data){
    for(let key in data){//遍历所有属性进行劫持
        let val = data[key];
        observe(val);//深入递归数据劫持exp:data:{a:{a:3},b:5}}
        Object.defineProperty(data,key,{
            enumerable: true,
            get(){
                return val//此时的val已经进行了数据劫持,exp:{a:3}
            },
            set(newVal){
                if(newVal === val ){//值不变则返回
                    return
                }
                val = newVal;
                observe(newVal);//新赋的值也必须进行数据劫持
            }
        }
    }
}

Object.defineProperty缺点是无法监听数组变化,vue是单独对数组进行了常用方法的hack。

proxy

Proxy在ES2015规范中被正式发布,它在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写,我们可以这样认为,Proxy是Object.defineProperty的全方位加强版,proxy可以监听整个对象,无论对象内部的属性是数组还是新对象。

proxy 劫持的简单例子:

const newObj = new Proxy(obj, {
  get: function(target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function(target, key, value, receiver) {
    console.log(target, key, value, receiver);
    return Reflect.set(target, key, value, receiver);
  },
});

被劫持的对象会被添加对应的watcher,以此实现双向数据绑定。

参考文章:

上一篇下一篇

猜你喜欢

热点阅读