vue基础扩展9-属性监测变更原理



  // 一、 vue 属性检测原理之基本案例

  var obj = {
    id : 1,
    name : 'zhangsan', 
    price : 20,
  }

  // var value = obj.price ; // 常规操作属性方式

  // 如何在获取或设置属性时能够做其他的操作呢? 此时我们引入一个叫Depend对象,它有二个方法 depend notify 

  class Depend {
    constructor() {}
    depend () {
      console.log('添加依赖'); // 如果调用get访问器,那么就是使用这个属性的,就添加到依赖
    }
    notify() {
      console.log('通知所有依赖变更')  // 如果set访问器执行,那么就通知所有添加的依赖对象。
    }
  }

  // 我们以price属性为例,通过defineProperty设置price的属性访问器模式,如果把所有属性都改成属性器那么就是Observer对象

  var depend = new Depend();

  var val = obj.price;

  Object.defineProperty(obj,'price',{
    configurable:true,
    enumerable:true,
    get() {
      depend.depend();
      return val;
    },
    set( newval) {
      depend.notify();
      val = newval;
    } 
  });

  console.log(obj.price); // 添加依赖 20

  obj.price = 20; // 通知所有依赖变更


  // 二、vue实现原理

  // 通过上面我们知道将一个对象所有属性全部变成get/set访问器模式,那么获取值的时候可以添加依赖,而如果值产生
  // 变更的时候就会通知所有依赖变更。我们把所有对象都变成set/get的Observer对象,并且实现了Depend依赖管理对象
  // ,但是我们还缺少一个监听器Watch对象

  // Watch对象就是监听某个对象的属性,通过访问器get获取值的时候,会自动添加依赖到Depend对象中,如果这个属性发生变更
  // 即Observer的set方法触发,就会触发depend的notify方法,notify方法实际调用的是Watch对象的update方法。

  // 1、Watch 对象最简单演示

  var temp = null; // 临时
  var depend = null; // 代替depend

  class Watch {
    constructor(obj,param,callback) {
      this.obj = obj;
      this.param = param;
      this.callback = callback;
      temp = this;
      this.value = obj[this.param];
      temp = null;
    }
    update () {
      const oldValue = this.value
      temp = this;
      this.value = this.obj[this.param];
      temp = null;
      this.callback.call(this.obj, this.value, oldValue);
    }
  }
  
  Object.defineProperty(obj,'price',{
    configurable:true,
    enumerable:true,
    get() { // depend.depend();  // 此处不使用Depend对象
      depend = temp;
      return val;
    },
    set( newval) {  // depend.notify();
      val = newval;
      depend.update();
    } 
  });

  var watch = new Watch(obj,'price' ,function(value,old) {
    console.log(value,old);
  });

  obj.price = 40;  // 40 , 20

  // 缺点,我们只用了一个depend变量来建立watch和observer的关系,显然是不行的。

  // 我们需要建立一个Depend类来管理所有的依赖关系,并且depend实例对应一个属性。

  // 即一个observer对应一个响应式对象,一个depend对应响应式对象一个属性,多个watch对象公用一个depend依赖管理

  
  // 2、完整案例
   
  // 类组成
  // Observer :观察者对象,将一个对象的所有属性变成get和set访问器模式。
  // Dep: 通过一个subs数组维护依赖对象,主要有depend添加依赖,notify通知所有依赖变更。
  // Watcher :监听对象,主要有get方法获取属性,添加自身到dep中,update方法为notify通知的变更

  //Observer类
  var Observer = function(obj) {
    this.obj = obj;
    walk(obj);
    function walk(obj) {
      const keys = Object.keys(obj);
      for (let i = 0; i < keys.length; i++) {
         defineReactive(obj,keys[i]);
      }
    }
    function defineReactive(obj,key,val) {
      if (arguments.length === 2) {
        val = obj[key];
      }
      if(typeof val === 'object') {
        new Observer(val); 
      }
      const dep = new Dep()
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get() {
          dep.depend(); // get方法添加到依赖管理
          return val;
        },
        set(newVal) { // set方法触发所有已经添加的依赖
          val = newVal;
          dep.notify();
        }
      });
    }
  }
  
  let uid = 0; // 计数器

  class Dep {
    constructor (){
      this.id = uid++; // 优化代码
      this.subs = [];
    }
    remove(sub) {
      this.subs = this.subs.filter(ele => {
        return ele != sub;
      });
    }
    addSub(sub) {
      this.subs.push(sub);
    }
    depend( ) {
      if (Dep.target) {
        // this.addSub(Dep.target); // 优化代码删除
        Dep.target.addDep(this); // 优化代码
      }
    }
    notify() {
      var subs = this.subs.slice();
      for (let i = 0; i < subs.length; i++) {
        subs[i].update();
      }
    }
  }
  // Dep类
  Dep.target = null;
  function pushTarget (target) {
    Dep.target = target
  }
  function popTarget () {
    Dep.target = null;
  }

  // Watcher类
  class Watcher {
    constructor (vm, expOrFn, callback) {
      this.depIds = new Set(); // 优化代码
      this.vm = vm;
      this.callback = callback;
      this.getter = parsePath(expOrFn)
      this.value = this.get()
    }
    get () {
      console.log('执行Watcher的get方法()');
      Dep.target = this;
      const vm = this.vm
      let value = this.getter.call(vm, vm)
      Dep.target = undefined;
      return value;
    }
    update () {
      const oldValue = this.value
      this.value = this.get()
      this.callback.call(this.vm, this.value, oldValue); // 触发回调函数
    } 
    addDep(dep) { // 优化代码
      const id = dep.id;
      if (!this.depIds.has(id)) {
        this.depIds.add(id);
        dep.addSub(this);
      }
    }
  }

  const bailRE = /[^\w.$]/;
  function parsePath (path) {
    if (bailRE.test(path)) {
      return
    }
    const segments = path.split('.');
    return function (obj) {
      for (let i = 0; i < segments.length; i++) {
        if (!obj) return
        obj = obj[segments[i]]
      }
      return obj
    }
  }


var vue = {
  id:'1',
  name:'zhangsan',
}
new Observer(vue);
new Watcher(vue, 'id', (val, oldValue)=>{
    console.log(val, oldValue); // 2 , id
});
vue.id = 2;
console.log('......');
vue.id = 3;
console.log('......');
vue.id = 4;

// 3、优化,从打印结果我们可以看出,我们多次执行了Watcher的get方法()
// 实际上我们只需要注册一次,即可,不需要多次执行。
// 解决办法: 通过Dep定义一个唯一的id,每一个Watcher只能添加一个依赖到同一dep解决。

// 3.1 Dep 添加代码

// let uid = 0; // 计数器

//  class Dep {
//   constructor (){
//       this.id = uid++; // 优化代码
//       this.subs = [];
//    }

// depend( ) {
//   if (Dep.target) {
//     // this.addSub(Dep.target); // 优化代码删除
//     Dep.target.addDep(this); // 优化代码
//   }
// }

// 3.2 watcher 优化代码

// constructor (vm, expOrFn, callback) {
//   this.depIds = new Set(); // 优化代码

// addDep(dep) { // 优化代码
//   const id = dep.id;
//   if (!this.depIds.has(id)) {
//     this.depIds.add(id);
//     dep.addSub(this);
//   }
// }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值