JavaScript手写响应式原理(详解)

news2025/1/13 15:55:41

响应式原理

首先我们有一个对象

  const obj = {
   name: 'zlk',
   age: 18
  }

这个对象可能在别处被用到

比如是这样的

function foo() {
   const newValue = obj.name
   console.log('hello world');
   console.log(obj.name);
  }

我们来改变obj对象中的name的值

obj.name = 'zlk'

这时候foo()应该被重新执行

那么我们如何让所以类似foo()的响应式重新执行一次呢

响应式函数封装

  watchFn(function foo() {
   const newValue = obj.name
   console.log('hello world');
   console.log(obj.name);
  })

我们再声明个数组

const reactiveFn = []

将响应式的函数push到数组中

  function watchFn(fn) {

   reactiveFn.push(fn)

  }

循环执行这个数组中的函数

  reactiveFn.forEach(fn => {

   fn()

  })

完整代码

const reactiveFn = []
function watchFn(fn) {
  reactiveFn.push(fn)
}
const obj = {
  name: 'zlk',
  age: 18
}
watchFn(function foo() {
  const newValue = obj.name
  console.log('hello world');
  console.log(obj.name);
})
watchFn(function demo() {
  console.log('响应式--2--');
})

function bar() {
  console.log('普通函数,无响应式');
}
reactiveFn.forEach(fn => {
  fn()
})

obj.name = 'zlk'

二,依赖收集类的封装

上面我们将需要响应的函数放进了数组里面,上面name发生改变需要重新执行函数,可是如果是age呢,如果还有别的响应式对象呢,频繁建数组?那很不方便管理

优点1:每一个属性对应一个类,每个类对应一个数组

优点2:我们可以在累里封装一个函数,遍历所有响应函数

重构

 class Depend {
   constructor() {this.reactiveFns = []
   }

   addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)
   }
   notify() {this.reactiveFns.forEach(fn => {fn()})
   }
  }



  const depend = new Depend()

  function watchFn(fn) {

   depend.addDepend(fn)

  }


  const obj = {

   name: 'zlk',

   age: 18

  }


  watchFn(function foo() {

   console.log('hello world');

   console.log(obj.name);

  })

  watchFn(function demo() {

   console.log('响应式--2--');

  })


  function bar() {

   console.log('普通函数,无响应式', obj.name);

  }


  obj.name = 'zlk'

  depend.notify()


  objProxy.name = 'zlk'

depend.notify()

自动监听对象变化

可是我们不能改一个对象中的值 depend.notify()一下

我们要让值被修改后,自动depend.notify(),执行响应式函数

所以我们用到了Proxy代理,当值被修改自动执行响应式函数

完整代码

  <script>
    class Depend {
      constructor() {
        this.reactiveFns = []
      }
      addDepend(reactiveFn) {
        this.reactiveFns.push(reactiveFn)
      }
      notify() {
        this.reactiveFns.forEach(fn => {
          fn()
        })
      }
    }

    const depend = new Depend()
    function watchFn(fn) {
      depend.addDepend(fn)
    }


    const obj = {
      name: 'zlk',
      age: 18
    }
    const objProxy = new Proxy(obj, {
      get(target, key, receiver) {
        return Reflect.get(target, key, receiver)
      },
      set(target, key, newValue, receiver) {
        Reflect.set(target, key, newValue, receiver)
        depend.notify()
      }
    })

    watchFn(function foo() {
      console.log('hello world');
      console.log(objProxy.name);
    })
    watchFn(function demo() {
      console.log('响应式--2--');
    })
    watchFn(function demo2() {
      console.log('响应式--2--', objProxy.age);
    })



    function bar() {
      console.log('普通函数,无响应式', objProxy.name);
    }


    objProxy.name = 'zlk'
    objProxy.name = 'aaa'
    objProxy.name = 'bbb'
    // depend.notify()
  </script>

依赖收集如何管理

可是我们又发现无论你修改name 还是age,所有响应式函数都会被执行

这不是我们想要的,我们的目的是修改name 执行用到name的函数自动执行,修改age执行用到age的响应函数,为什么呢,因为他们只有一个depend对象,没有进行区分。我们使用map wekmap来区分

我们封装一个获取depend函数,这里的数据结构是这样的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-612hSbze-1672193482993)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20221228094317352.png)]

  class Depend {

   constructor() {this.reactiveFns = []
   }

   addDepend(reactiveFn) {this.reactiveFns.push(reactiveFn)
   }
   notify() {this.reactiveFns.forEach(fn => {fn()})
   }
  }

  const depend = new Depend()
  function watchFn(fn) {
   depend.addDepend(fn)
  }

  //获取depend函数

  const targetMap = new WeakMap()

  function getDepend(target, key) {

   let map = targetMap.get(target)

   if (!map) {

​    map = new Map()

​    targetMap.set(target, map)

   }


   let depend = map.get(key)

   if (!depend) {

​    depend = new Depend()

​    map.set(key, depend)

   }

   return depend

  }



  const obj = {

   name: 'zlk',

   age: 18

  }


  //自动监听收集依赖

  const objProxy = new Proxy(obj, {

   get(target, key, receiver) {return Reflect.get(target, key, receiver)

   },

   set(target, key, newValue, receiver) {

​    Reflect.set(target, key, newValue, receiver)

​    console.log(target, key);const depend = getDepend(target, key)

​    console.log(depend.reactiveFns);

​    depend.notify()

   }

  })



  watchFn(function foo() {

   console.log('hello world');

   console.log(objProxy.name);

  })

  watchFn(function demo() {

   console.log('响应式--2--');

  })

  watchFn(function demo2() {

   console.log('响应式--3--', objProxy.age);

  })





  function bar() {

   console.log('普通函数,无响应式', objProxy.name);

  }


  objProxy.name = 'zlk'

  objProxy.name = 'aaa'

  objProxy.name = 'bbb'

map 和weakMap 响应式 知识补充,方便理解上面代码,如果上面不理解,先把下面这段搞懂再去理解上面代码

 const obj1 = {

   name: "why",

   age: 18

  }



  const obj2 = {

   name: "why",

   age: 18

  }



  // 1.创建WeakMap

  const weakMap = new WeakMap();



  // 2. 收集依赖结构

  // 2.1 使用map来收集

  const obj1Map = new Map();

  obj1Map.set("name", [obj1GetName, obj1SetName])

  obj1Map.set("age", [obj1GetAge, obj1SetAge])

  weakMap.set(obj1, obj1Map)




  // 3.如果obj1.name发生改变

  // Proxy/Object.defineProperty

  obj1.name = "test";

  const targetMap = weakMap.get(obj1);

  const fns = targetMap.get("name")

  fns.forEach(item => item())



  function obj1GetName() {

   console.log("obj1GetName")

  }



  function obj1SetName() {

   console.log("obj1SetName")

  }


  function obj1GetAge() {

   console.log("obj1GetAge")

  }


  function obj1SetAge() {

   console.log("obj1SetAge")

  }

最终完整收集依赖

你发现上面depend.reactiveFns打印出来为[ ]了吗?

我们现在想如何把响应函数自动放到数组里,然后去执行

proxy的get里面写

const depend = getDepend(target, key)

depend.addDepend(dependFN)

一步步debugger,你会了解整个流程,我这边简单说一下帮助理解

你先执行watchFn函数,将响应式函数传入,途中将函数赋值给全局变量,传入后执行响应函数代码块,因为里面用到代理监听对象,所以它会去往proxy中的get中,将去创建自己的depend对象,创建好后,将全局变量中的响应函数代码块放入创建类里面数组中,之后如果你设置修改了值,将去往proxy中set方法重新执行数组中响应函数。

 class Depend {

   constructor() {this.reactiveFns = []  

   }

   addDepend(reactiveFn) {debuggerthis.reactiveFns.push(reactiveFn)

   }

   notify() {debuggerthis.reactiveFns.forEach(fn => {fn()})

   }

  }



  let dependFN = null //全局变量

  function watchFn(fn) {

   debugger

   dependFN = fn  //将响应函数放到全局变量里,让proxy中的get中可以获取到

   fn() //执行响应函数
  }





  //获取depend函数

  const targetMap = new WeakMap()

  function getDepend(target, key) {

   debugger

   let map = targetMap.get(target)

   if (!map) {

​    map = new Map()

​    targetMap.set(target, map)

   }



   let depend = map.get(key)

   if (!depend) {

​    depend = new Depend()

​    map.set(key, depend)

   }

   return depend

  }



  const obj = {

   name: 'zlk',

   age: 18

  }





  //自动监听收集依赖

  const objProxy = new Proxy(obj, {

   get(target, key, receiver) {debuggerconst depend = getDepend(target, key)

​    console.log(depend);

​    depend.addDepend(dependFN)return Reflect.get(target, key, receiver)

   },

   set(target, key, newValue, receiver) {debugger

​    Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)

​    depend.notify()

   }

  })



  watchFn(function foo() {

   debugger

   console.log('响应式1name', objProxy.name, objProxy.age);

  })



  watchFn(function demo2() {

   console.log('响应式2age', objProxy.age);

  })





  // function bar() {

  //  console.log('普通函数,无响应式', objProxy.name);

  // }





  objProxy.name = 'zhf'



  console.log('--------------------------');

  objProxy.age = 24

vue3响应式原理

1,因为上面我们只有一个对象obj ,可是如果有多个呢,你将要写多个代理?我们还是封装个函数吧,把代理封装到reactive函数中,这时候你是不是发现和vue3中的reactive一样了。

2,创建数组reactiveFns时我们改成了set,set和数组一样,只不过多了去重的功能。是为了如果有这样的代码执行,代码会被执行两次

watchFn(function foo() {
debugger
console.log(‘响应式1name–1’, objProxy.name);
console.log(‘响应式1name–2’, objProxy.name);
})
在这里插入图片描述

  class Depend {
      constructor() {
        this.reactiveFns = new Set()
      }
      addDepend(reactiveFn) {
        debugger
        this.reactiveFns.add(reactiveFn)
      }
      notify() {
        debugger
        this.reactiveFns.forEach(fn => {
          fn()
        })
      }
    }

    let dependFN = null
    function watchFn(fn) {
      debugger
      dependFN = fn
      fn()


    }


    //获取depend函数
    const targetMap = new WeakMap()
    function getDepend(target, key) {
      debugger
      let map = targetMap.get(target)
      if (!map) {
        map = new Map()
        targetMap.set(target, map)
      }

      let depend = map.get(key)
      if (!depend) {
        depend = new Depend()
        map.set(key, depend)
      }
      return depend
    }

    const objProxy = reactive({
      name: 'zlk',
      age: 18
    })
    //自动监听收集依赖
    function reactive(obj) {
      return new Proxy(obj, {
        get(target, key, receiver) {
          debugger
          const depend = getDepend(target, key)
          console.log(depend);
          depend.addDepend(dependFN)
          return Reflect.get(target, key, receiver)
        },
        set(target, key, newValue, receiver) {
          debugger
          Reflect.set(target, key, newValue, receiver)
          const depend = getDepend(target, key)
          depend.notify()
        }
      })
    }


    watchFn(function foo() {
      debugger
      console.log('响应式1name', objProxy.name);
    })

    watchFn(function demo2() {
      console.log('响应式2age', objProxy.age);
    })
    console.log('--------------------------改变后');


    // function bar() {
    //   console.log('普通函数,无响应式', objProxy.name);
    // }


    objProxy.name = 'zhf'

vue2响应式原理

proxy代理替换为object.defineProperty
在这里插入图片描述

次文章需要理解的api
proxy-Reflect
map和weakmap
class类封装构造函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124880.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android设计模式详解之代理模式

前言 代理模式也称为委托模式&#xff0c;是一种结构型设计模式&#xff1b; 定义&#xff1a;为其他对象提供一种代理以控制对这个对象的访问&#xff1b; 使用场景&#xff1a;当无法或不想直接访问某个对象或访问某个对象存在困难时&#xff0c;可以通过一个代理对象来间…

css实现圆环、渐变色圆环的多种方式

css实现圆环、渐变色圆环的多种方式一、实现圆环方法具体如下&#xff1a;1. 两个div标签的叠加2.使用伪元素&#xff0c;before&#xff0f;after3. 使用border4. 使用border-shadow5. 使用radial-gradient二、实现渐变色圆环方法具体如下&#xff1a;1.background:linear-gra…

详细记录拉链表的实现过程

面试中被问到了&#xff0c;想了会儿思路混乱没答好&#xff0c;还是理解的不够深刻&#xff0c;重新好好理解记录一下~ 拉链表的用途&#xff0c;主要是用来在数仓中记录业务库数据的全部历史信息和当前最新信息&#xff0c;也就是用来实现对渐变维的记录。数仓中对渐变维的记…

.NET和JavaScript控件丨Infragistics功能简介

使用Infragistics Ultimate UI/UX工具包简化开发&#xff0c;提供综合的企业级 UI控件库和使用Indigo.Design的 UX设计-开发协作工具 -一个完整的设计到代码系统- 集成原型、设计系统、用户测试、应用程序构建和代码生成。 终极开发者工具包 为任何平台上的任何设备设计、现代…

minikube start

因为要安装的中间件需要运行在k8s的环境里。官方推荐用minikube 运行minikube start时遇到问题。 容器下载速度为0 &#xff08;没有截屏&#xff09;kubectl初始化超时 initial timeout of 40s passed: 解决问题1&#xff0c;需要加上–image-mirror-countrycn’参数。如果…

长短期记忆网络(LSTM)

长短期记忆网络有三种类型的门&#xff1a;输入门、遗忘门和输出门。 长短期记忆网络的隐藏层输出包括“隐状态”和“记忆元”。只有隐状态会传递到输出层&#xff0c;而记忆元完全属于内部信息。 长短期记忆网络可以缓解梯度消失和梯度爆炸。 由于序列的长距离依赖性&#…

27移除元素--双指针(快慢指针)

27移除元素–双指针&#xff08;快慢指针&#xff09; 移除元素这道题看起来很简单&#xff0c;但其蕴含的快慢指针的思想十分重要。 双for循环&#xff08;暴力法&#xff09;-- O(n2n^2n2) 使用第1个for循环 i 遍历数组所有元素 使用第2个for循环从 i 开始进行数组元素的前移…

骨传导耳机伤耳朵吗、骨传导耳机适合适用的人群有哪些?

事实上&#xff0c;骨传导耳机是对耳朵最健康的一种耳机了&#xff0c;下面就来详细说说这种耳机。 骨传导耳机是以人的骨骼为介质&#xff0c;不经过外耳道和耳膜&#xff0c;将声音传递给听觉器官的耳机。他对人的耳朵损害相比起传统的耳机损害更小&#xff0c;因为听力受损…

JavaSE笔记——Lambda表达式

文章目录前言一、第一个Lambda表达式二、如何辨别Lambda表达式三、引用值&#xff0c;而不是变量四、函数接口五、类型推断总结前言 Java 8 的最大变化是引入了 Lambda 表达式——一种紧凑的、传递行为的方式。 一、第一个Lambda表达式 Swing 是一个与平台无关的 Java 类库&a…

redhat7.6+grid 11.2.0.4部署遇到各种问题

一、add cluster node时&#xff0c;卡住 两个节点时间不同步&#xff0c;设置时间同步即可 二、部署Redhat7.6oracle11g部署中的bug Oracle 11.2.0.4 部署rac过程中&#xff0c;需要运行root.sh脚本报错。提示&#xff1a; ohasd集群无法启动。该补丁修改ohasd无法启动的问题…

红外成像系统测试

通常人们把红外辐射称为红外光、红外线。实际上其波段是指其波长约在0.75μm到1000μm的电磁波。人们将其划分为近、中、远红外三部分。近红外指波长为0.75-3.0μm;中红外指波长为3.0-20μm;远红外则指波长为20-1000μm。由于大气对红外辐射的吸收,只留下三个重要的“窗口”…

一把巴枪,和被改变的菜鸟驿站站长们

成立9年的菜鸟物流一直在答题。如果说之前这张答卷更多的标签是面向物流前端的配送和分拣等&#xff0c;那么如今&#xff0c;它的更多答案已经不单纯是前端的流通和连接&#xff0c;更有最末端基于科技对人的温度和赋能。 作者|丰兰 出品|产业家 数字化&#xff0c;正在…

少儿Python每日一题(6):角谷猜想

原题解答 本次的题目如下所示&#xff08;原题出处&#xff1a;NOC&#xff09;&#xff1a; 角谷猜想&#xff1a;以一个正整数n为例&#xff0c;如果n为偶数&#xff0c;就将它变为n/2&#xff1b;如果除后变成奇数&#xff0c;则将它乘3加1&#xff08;即3n1&#xff09;。…

latex常用语法速查

本文针对overleaf在线使用latex的情况编写。 文章目录文档结构要点导入图片使用表格添加引用参考资料文档结构 文档类型设置 \documentclass[12pt,article]{book} % []中设置文档格式&#xff0c;文档字体大小默认为10pt&#xff0c;article指定文档用纸类型&#xff0c;其他…

【金猿人物展】龙盈智达首席数据科学家王彦博:量子科技为AI大数据创新发展注入新动能...

‍王彦博本文由龙盈智达首席数据科学家王彦博撰写并投递参与“数据猿年度金猿策划活动——2022大数据产业趋势人物榜单及奖项”评选。‍数据智能产业创新服务媒体——聚焦数智 改变商业回顾2022年大数据行业发展&#xff0c;令人感触最深的是数字经济时代对“数据安全”和“数…

基于自主可控的新型基础测绘与实景三维中国建设

实景三维中国作为强赋能、稳基底、重应用的新型基础设施&#xff0c;是打造数字中国、数字经济、数字政府的核心资源&#xff0c;其关键技术的掌握已经成为撬动社会生产&#xff0c;促进行业良性内循环&#xff0c;引发国家数字资源合理分配的重中之重。 ▲实景三维工程技术研究…

小程序入门01

目录 1.什么是小程序 2.小程序可以干什么&#xff1f; 3.相关资料 4.入门 4.1 申请账号 4.2 安装第一个小程序 4.3 了解程序 1.什么是小程序 2017年度百度百科十大热词之一 微信小程序&#xff08;wei xin xiao cheng xu&#xff09;&#xff0c;简称小程序&#xff0c;英文…

FreeSWITCH在视频会议中的实践经验

点击上方“LiveVideoStack”关注我们▲扫描图中二维码或点击阅读原文▲了解音视频技术大会更多信息// 编者按&#xff1a;视频会议已成为日常办公不可或缺的一部分&#xff0c;为远程交流的人们提供了许多便利。本次RTSCon 2022会议&#xff0c;由RTS社区和LiveVideoStack音视…

Win10的两个实用技巧系列之设置鼠标指针、红警玩不了怎么办?

win10系统怎么设置鼠标指针在打字时隐藏? win10隐藏鼠标指针的方法 win10系统怎么设置鼠标指针在打字时隐藏&#xff1f;win10系统输入文字的时候&#xff0c;想要隐藏鼠标指针&#xff0c;该怎么操作呢&#xff1f;下面我们就来看看win10隐藏鼠标指针的方法 win10如何隐藏鼠…

Android中的属性动画

在属性动画出来之前&#xff0c;Android系统提供的动画只有帧动画和View动画。View动画大家可能知道&#xff0c;它提供了AlphaAnimation(透明度)&#xff0c;RotateAnimation(负责旋转)&#xff0c;TranslateAnimation(负责移动)&#xff0c;ScaleAnimation(负责缩放)这4种动画…