重写一遍vue响应式原理过程

首先理解响应式逻辑

  1. 知道收集视图依赖了哪些数据
  2. 感知被依赖数据的变化
  3. 数据变化时,自动“通知”需要更新的视图部分,并进行更新

01.简单编写响应式手动收集依赖

const obj = {
  name: "james",
  age: 38,
};
//定义一个函数,专门执行响应式函数
const reactiveFns = [];

function watchFn(fn) {
  reactiveFns.push(fn);
  fn();
  //函数保存起来,默认执行一次
  //类似于wachEeffct(),自动收集依赖,立即执行执行一次
}
//凡是传进watchFn函数里面就是响应式的
watchFn(function foo() {
  console.log("foo name is", obj.name);
  console.log("foo age is ", obj.age);
});
watchFn(function bar() {
  console.log("bar name is", obj.name + "kobe");
  console.log("bar age is ", obj.age + 10);
});

console.log("监听age发生变化~~~~~~~~");
obj.age += 100;
//把之前依赖的函数遍历出来执行
reactiveFns.forEach((fn) => {
  fn();
});

//目前存在缺点,
// + 在实际开发中,响应式对象肯定不止一个
// + 目前还不能自动收集依赖

02.对之前的版本改进,创建一个类Depend,用类去管理所有收集的函数

class Depend {
  constructor() {
    this.reactiveFns = [];
  }
  //添加对应的依赖
  addDepend(fn) {
    if (fn) {
      this.reactiveFns.push(fn);
    }
  }
  //通知数据发生变化,收集依赖
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}

这样在使用的时候只需要new Depend()调用即可

const dep = new Depend();

function watchFn(fn) {
  dep.addDepend(fn);
  fn();
  //函数保存起来,默认执行一次
  //类似于wachEeffct(),自动收集依赖,立即执行执行一次
}
//当obj的值发生改变时-----

//手动更新通知
dep.notify();

// 目前存在的缺点:
// + obj值发生变化,还是需要手动去调用notify收集依赖

03.监听属性变量,自动去收集依赖更新

  • 监听属性变量

到这一步时候,可以用vue2的obj.definproperty()数据劫持,或者vue3de new Proxy()去监听对象属性

//方案一。Object.defineProperty() -> Vue2
//对obj里面的数据进行遍历
Object.keys(obj).forEach((key) => {
  let value = obj[key];
  //对每个key和set操作进行对应劫持
  Object.defineProperty(obj, key, {
    set: function (newValue) {
      //保存value值
      value = newValue;
      //去通知更新
      dep.notify();
    },
    get: function () {
      return value;
    },
  });
});

目前是对象属性发生改变,不用再去手动调用notify()更新

  • 但是目前实现到这里,出现一个错误情况,那就是收集依赖既收集name的,又同时收集age的。如果只改变name的值,age的依赖也同时更新发生变化了,这种做法是错误的。

  • 想要实现的,是在当前属性发生变化的时候,收集依赖只能是当前属性的依赖,通知更新也应当前属性的更新,所以这里需要解决这个问题,应该是一一对应的关系,自动去收集依赖

    1. dep对象数据结构的管理
    • 每一个对象的每一个属性都会对应一个dep对象
    • 同一个对象的多个属性的dep对象是存放一个map对象中
    • 多个对象的map对象, 会被存放到一个objMap的对象中
    1. 依赖收集: 当执行get函数, 自动的添加fn函数
1668667528714.png

所以这种做法是不要的。

  • 重新封装执行自动收集依赖

//定义一个函数,专门执行响应式函数

//形成闭包
let reactiveFn = null;

function watchFn(fn) {
  //一旦执行函数,将值赋值给reactiveFn
  reactiveFn = fn;
  fn();
  //函数保存起来,默认执行一次
  //类似于wachEeffct(),自动收集依赖,立即执行执行一次

  reactiveFn = null;
  //执行完操作之后,赋值为null,有可能对后续操作造成影响
}

//封装一个函数,来获取对应的depend对象
//利用weakMap,弱引用,即使obj为null,也不能对后续造成影响,
const objMap = new WeakMap();
function getDepend(obj, key) {
  //根据对象obj,找到对应的map对象
  let map = objMap.get(obj);
  if (!map) {
    //weakMap的key必须要是对象,所以这里用Map()
    //如果拿不到,我们就new一个
    map = new Map();
    objMap.set(obj, map);
  }
  //根据key,找到对应的depend对象
  let dep = map.get(key);
  if (!dep) {
    dep = new Depend();
    map.set(key, dep);
  }
  //所以一定可以返回拿到一个dep对象
  return dep;
}
//方案一。Object.defineProperty() -> Vue2
//对obj里面的数据进行遍历
Object.keys(obj).forEach((key) => {
  let value = obj[key];
  //对每个key和set操作进行对应劫持
  Object.defineProperty(obj, key, {
    set: function (newValue) {
      //保存value值
      value = newValue;
      const dep = getDepend(obj, key);
      //去通知更新
      dep.notify();
    },
    get: function () {
      // 拿到obj -> key
      // 找到对应的obj对象的key对应的dep对象
      const dep = getDepend(obj, key);
      //间接的把函数添加进去
      dep.addDepend(reactiveFn);

      return value;
    },
  });
});

此时已经完成可以自动收集依赖更新,但是还存在俩个问题:

  • 如果监听重复obj.age的代码,它会重复执行一遍
watchFn(function() {
  console.log(obj.name)
  console.log(obj.age)
  console.log(obj.age)
})
  • 只是只是针对单个对象,还不能针对多对象

04.最后完善优化,封装函数针对多对象调用

  • 针对于重复执行问题,可以在类Depend里面进行if判断,或者直接使用Set()
constructor() {
    //set不允许重复
    this.reactiveFns = new Set();
  }
  • 针对多对象使用问题,

    • 封装成一个reactive函数返回出去,让外面的对象在使用的时包裹一层reactive()

      function reactive(obj) {
        Object.keys(obj).forEach((key) => {
          let value = obj[key];
          //对每个key和set操作进行对应劫持
          Object.defineProperty(obj, key, {
            set: function (newValue) {
              //保存value值
              value = newValue;
              const dep = getDepend(obj, key);
              //去通知更新
              dep.notify();
            },
            get: function () {
              // 拿到obj -> key
              // 找到对应的obj对象的key对应的dep对象
              const dep = getDepend(obj, key);
              //间接的把函数添加进去
              dep.depend();
      
              return value;
            },
          });
        });
        return obj;
      }
      
      
      //在使用时
      const obj = reactive({
        name: "james",
        age: 38,
      });
      

到这一步已实现vue2的响应式原理,实现vue3的响应式原理那就太简单,直接把Object.defineProperty换成Proxy去实现就行了

  • vue2与vue3响应式原理的区别:
  • Vue2采⽤数据劫持并结合了发布者-订阅者模式,通过 Object.defineProperty()来劫持各个属性setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调
  • Vue3放弃了Object.defineProperty API,⽽使⽤了更快的Proxy API。Proxy 是在 ES6 中引⼊,它允许你拦截对该对象的任何交互,也可以避免 Vue 早期版本中存在的⼀些响应性问题。

05.写好的最终代码:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容