Vue通过Object.defineProperty来实现监听数据的改变和读取(属性中的getter和setter方法) 实现数据劫持。下面简单记录一下,vue监听数据变化的原理

defineProperty

defineProperty:可以直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。Object.defineProperty(obj, prop, descriptor);

obj: 需要操作的对象
prop: 需要操作的属性
descriptor: 属性描述符

 // 例如: 给obj对象动态新增一个name属性, 并且name属性的取值必须是lnj 
let obj = {name: '李白'};
 Object.defineProperty(obj, 'name', {
        value: 'lnj', // 新增的属性的取值
        // writable: true,// 默认情况下新增的属性的取值不能修改
        writable: true
        configurable: true,  // 默认情况下新增的属性不能删除的
        enumerable: true // 默认情况下y新增的属性不能迭代
    });
    console.log(obj);

defineProperty get/set方法

只要通过defineProperty给某个属性添加了get/set方法,那么以后只要获取这个属性的值就会自动调用get, 设置这个属性的值就会自动调用set。如果设置了get/set方法, 那么就不能通过value直接赋值, 也不能编写writable:true

 let obj = {};
    let oldValue = '李白';
    Object.defineProperty(obj, 'name', {
        // value: oldValue,
        // writable: true,
        configurable: true,
        enumerable: true,
        get(){
            console.log("get is executed");
            return oldValue;
        },
        set(newValue){
            if(oldValue !== newValue){
                console.log("set is executed");
                oldValue = newValue;
            }
        }
    });
    console.log(obj.name);
    obj.name = 'lnj';

监听数据变化

let obj = { name: 'lnj', };
    class Observer{
        constructor(data){
            this.observer(data);
        }
        observer(obj){
            if(obj && typeof obj === 'object'){
                for(let key in obj){
                    this.defineRecative(obj, key, obj[key])
                }
            }
        }
        defineRecative(obj, attr, value){
            this.observer(value);
            Object.defineProperty(obj, attr, {
                get(){
                    return value;
                },
                set:(newValue)=>{
                    if(value !== newValue){
                        this.observer(newValue);
                        value = newValue;
                        console.log('数据变化,更新界面');
                    }
                }
            })
        }
    }
    new Observer(obj);
  obj.name = {name: 'abc'};

结合发布/订阅模式

参考地址:https://segmentfault.com/a/1190000013338801

在监听到数据发生变化之后,就需要通知界面更新数据,那如何通知界面更新数据呢?

1)先定义一个Watcher(观察者类),用于判断新值和旧值是否相等,即是否发生变化

2)再定义一个Dep(发布订阅类),用于统一管理所有的Watcher

  • Dep 负责收集所有相关的的订阅者 Watcher ,具体谁不用管,具体有多少也不用管,只需要根据 target 指向的计算去收集订阅其消息的 Watcher 即可,然后做好消息发布 notify 即可。
  • Watcher 负责订阅 Dep ,并在订阅的时候让 Dep 进行收集,接收到 Dep 发布的消息时,做好其 update 操作即可。 

 1、在第一次渲染时,就给所有属性添加观察者(订阅消息)

let CompilerUtil = {
  // 获取data里面的值
   getValue(vm, value){
       return value.split('.').reduce((data, currentKey) => {
            return data[currentKey.trim()];
        }, vm.$data);
    },
 // 自定义v-model
  model: function (node, value, vm) {
       // 在第一次渲染的时候, 就给所有的属性添加观察者
        new Watcher(vm, value, (newValue, oldValue)=>{
            node.value = newValue;
        });
        let val = this.getValue(vm, value);
        node.value = val;
    },
 .......
}
class Dep {
    constructor(){
        this.subs = []; // 收集某个属性所有的观察者对象的
    }
    // 订阅观察的方法
    addSub(watcher){
        this.subs.push(watcher);
    }
    // 发布订阅的方法
    notify(){
        this.subs.forEach(watcher=>watcher.update());
    }
}
class Watcher {
    constructor(vm, attr, cb){
        this.vm = vm;
        this.attr = attr;
        this.cb = cb;
         // 在创建观察者对象的时候就去获取当前的旧值
        this.oldValue = this.getOldValue();
    }
    getOldValue(){
        Dep.target = this;
        let oldValue = CompilerUtil.getValue(this.vm, this.attr);
        Dep.target = null;
        return oldValue;
    }
    update(){
        let newValue = CompilerUtil.getValue(this.vm, this.attr);
        if(this.oldValue !== newValue){
            this.cb(newValue, this.oldValue);
        }
    }
}

 2、在劫持数据的时候(definProperty),将当前属性的所有观察者对象都放到当前属性的发布订阅对象中管理起来

 class Observer{
        constructor(data){
            this.observer(data);
        }
        observer(obj){
            if(obj && typeof obj === 'object'){
                for(let key in obj){
                    this.defineRecative(obj, key, obj[key])
                }
            }
        }
        // 将当前属性的所有观察者对象都放到当前属性的发布订阅对象中管理起来
        let dep = new Dep(); // 创建了属于当前属性的发布订阅对象
        defineRecative(obj, attr, value){
            this.observer(value);
            Object.defineProperty(obj, attr, {
                get(){
                    Dep.target && dep.addSub(Dep.target);
                    return value;
                },
                set:(newValue)=>{
                    if(value !== newValue){
                        this.observer(newValue);
                        value = newValue;
                         dep.notify();
                        console.log('数据变化,更新界面');
                    }
                }
            })
        }
    }

响应式原理完整代码https://www.yuque.com/docs/share/3eebc7fd-21f0-4ab3-bad5-74ffa69a372c?# 《响应式原理》https://www.yuque.com/docs/share/3eebc7fd-21f0-4ab3-bad5-74ffa69a372c?#%20%E3%80%8A%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86%E3%80%8B

Logo

前往低代码交流专区

更多推荐