vue数据监测机制--observer

3,370 阅读3分钟

vue的mvvm模型,解耦了视图和数据,为前端开发提供了极大的便利,而其中最重要的是数据变化检测,vue的data检测机制有以下几个特点:

  • 检测数据为对象的时候,必须先声明属性 ,这个属性才是响应式的。
  • 增加不存在的属性 不能更新视图 (vm.$set)
  • 修改数组索引和长度 是不会导致视图更新的
  • 数组里套对象 对象是支持响应式变化的,如果是常量则没有效果
  • 如果新增的数据 vue中也会帮你监控(对象类型)

Object的变化检测

在js中有两种方法可以侦测到对象的变化Object.definePropertyES6的Proxy,vue2.0的版本是采用Object.defineProperty来检测对象数据的变化。根据vue的使用特性,我们要检测对象的所有key值,并且对新增的数据也要检测,我们可以写出下面的代码:

function observer(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return obj;
    }

    // 监听对象的每一个属性 key
    Object.keys(obj).forEach(key=>{
        defineReactive(obj,key,obj[key])    
    })
}

function defineReactive(obj,key,value) {
    // 递归对象的值,如果值为对象,也监测
    observer(value);
    Object.defineProperty(obj,key,{
        get() {
            // 对于对象 我们在这里 收集依赖 watcher
            return value
        },
        set(newValue) { 
            //给某个key设置值的时候 可能也是一个对象 也需要监听
            observer(newValue);
            //对象: 在这里触发收集的依赖
            value = newValue;
             console.log('视图更新');
        }
    })
}

对于对象数据的类型处理,我们使用递归来检测对象的每一个属性,同时,我们也可以给对象属性赋的新值也可能是个对象,由于Object.defineProperty只能监测对象已声明的属性,对于新的对象,我们也要再次调用observer(newValue)。同时,vue无法检测obj在定义时,没有声明的而后来新增的属性如name,或者是delete this.obj.name这个变化,也就不会更新视图。

Array的变化侦测

我们已经知道了Object的侦测方式是通过getter/setter来实现的,但是更改数组的方法有push、pop、shift、unshift、splice、sort、reverse这些原型方法,这种方式是 getter/setter办不到的。 因此要检测数组的变化,我们必须自己来实现,所有vue对为数组提供了一个拦截器,对于要检测的数组都使用拦截器上的方法。 拦截器的实现

const arraryProto = Array.prototype;
// 数组原型上的方法
let proto = Object.create(arrayProto);
['push', 'unshift', 'splice', 'reverse', 'sort', 'shift', 'pop'].forEach(method=>{
    proto[method] = function (...args) {
        // 和 Object一样,我们也要处理 数组的新增数据 ,push unshift 和 splice都可以新增数据
        let inserted; // 默认没有插入新的数据
        switch(method) {
            case 'push':
            case 'unshift':
                inserted = args
                break;
            // 数组的splice 只有传递三个参数 才是往数组增加数据
            case 'splice':
                inserted = args.slice(2)
                break;
            default:
                break;
        }
         console.log('视图更新');
        // 检测新增的数据
        ArrayObserver(inserted)
        // 还是调用数组的原型方法,但是我们可以在这里 发送数组的变化通知
        arrayProto[method].call(this, ...args)
    } 
})

ArrayObserver的实现

function ArrayObserver(obj) {
    for(let i=0;i<obj.length;i++) {
        let item = obj[i];
        // 如果是普通值 就不监控了
        observer(item); // 如果是对象会被 defineReactive
    }
}

在最上面实现的observe函数中,我们还需要加上数组的侦测逻辑:

function observer(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return obj;
    }
    // 数组的处理逻辑
    if(Array.isArray(obj)) {
        // 对于要侦测的数组要加上拦截器,数组的方法进行重写
        Object.setPrototypeOf(obj,proto)
        ArrayObserver(obj);
    }else{
        // 对象的处理
        Object.keys(obj).forEach(key=>{
            defineReactive(obj,key,obj[key])    
        })
    }
}

数组的依赖列表是挂在observer函数上的,最有我们就可以在getter和拦截器中都可以访问到依赖列表。

小结

至此,vue中关于侦测对象Object和数组Array的原理已基本实现,但是光有数据侦测机制,而没有依赖,是不能把数据(model)和视图(view)连接起来的。具体怎么收集和触发依赖,后面再分享。