当前位置: 首页 > news >正文

Vue响应式数据的实现原理(手写副作用函数的存储和执行过程)

1.命令式和声明式框架

命令式框架关注过程

声明式框架关注结果(底层对命令式的DOM获取和修改进行了封装)

2.vue2 Object.defineProperty()双向绑定的实现

<body><div id="app"><input type="text" /><h1></h1><button>按钮</button> </div>
</body>
<script>//   vue2实现双绑const input = document.getElementsByTagName('input')[0]const h1 = document.getElementsByTagName('h1')[0]const btn = document.getElementsByTagName('button')[0]let data = { text: '' }// input框输入数据,h1数据和text一致;实现点击按钮时,h1 标签数据和input框数据同时更改Object.defineProperty(data, 'text', {get() {return data['text'];},set(value) {// 获取到值后将h1后的内容设置为texth1.innerText = value;input.value = value;return true;}});input.oninput = function (e) {data.text = e.target.value;}btn.onclick = function () {data.text = "你好"}
</script>

3.同样页面Vu3实现 new Proxy()

        // vue3实现双绑const input = document.getElementsByTagName('input')[0]const h1 = document.getElementsByTagName('h1')[0]const btn = document.getElementsByTagName('button')[0]let data = { text: '' }let obj = new Proxy(data, {get(target, property) {return target[property]},set(target, property, value) {h1.innerText = value;input.value = value;return true;}})input.oninput = function (e) {obj.text = e.target.value;}btn.onclick = function () {obj.text = "你好"}

4.响应式数据的基本实现

响应式数据的关键是拦截对象属性的设置和读取操作

const data = { text: '' }
function effect () {document.body.innerText = data.text
}

5. vue2与vue3响应式数据实现区别

  • vue2的实现:当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用Object.defineProperty() 把这些 property 全部转为 getter/setter。
  • vue3的实现:当我们从一个组件的 data 函数中返回一个普通的 JavaScript 对象时,Vue 会将该对象包裹在一个带有 get 和 set 处理程序的Proxy 中

6.vue3 proxy的简单实现响应式数据拦截

// 初始数据
const data = { text: '' }
// 存储副作用函数的桶
const bucket = new Set()
// 对数据进行代理
const obj = new Proxy(data, {get(target, key) {bucket.add(effect)return target[key]},set(target, key, newVal) {target[key] = newValbucket.forEach(fn => fn())return true}
})function effect () {document.body.innerText = obj.text
}effect()setTimeout(() => {obj.text = '你好'
}, 1000)

7.简单实现中出现的问题

思考一下 这一段代码的问题。

  • 1. 副作用函数的名称被写死
  • 2. 没有建立副作用函数和目标字段之前明确的关系

副作用函数的名称被写死——解决:

名称写死问题:真实情况下副作用函数不可能只有一个,那如果有多个函数时,每个函数执行都会调用一个副作用函数。比如,设置obj.a = 2,同样会调用set方法中的bucket.forEach(fn => fn())方法。

通用一个副作用作用函数,将执行DOM修改的函数以闭包形式(回调函数)传入副作用函数,这样每次返回的都不是同一个函数

let activeEffectfunction effect(fn) {activeEffect = fnfn()
}effect(() => {document.body.innerText = obj.text
})const obj = new Proxy(data, {get(target, key) {if (activeEffect) {bucket.add(activeEffect)}return target[key]},set(target, key, newVal) {target[key] = newValbucket.forEach(fn => fn())return true}
})

没有建立副作用函数和目标字段之前明确的关系——解决:

以上代码对每个属性都绑定了同一个副作用函数,实际上真实需要的时,修改text时,调用他自己的函数,修改a时又调用a对应的函数

解决:

  1. 使用Map键值数据结构(分两层)进行存储副作用函数,将每个数据对象对应一个map的键;
  2. 一个数据对象下不同属性又存储到一个Map数据下,这个map的值则存储的副作用函数(Set形式存)
  3. 使用时通过获取对象的map(数据对象)下的对应属性key(每个对象的key)的值的set数据(针对每个属性操作的副作用函数)并进行遍历执行即可

const obj = new Proxy(data, {get(target, key) {console.log(activeEffect, 'activeEffect')// 没有副作用函数,容错处理 直接returnif (!activeEffect) return target[key]// 判断桶中是否已经存在key与target的关联关系let depsMap = bucket.get(target)// 创建一个新的Map结构与 target 关联if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 判断当前Map数据中是否存在key与effect的关联关系let deps = depsMap.get(key)// 不存在的话 则新建一个Set 与 key关联if (!deps) {depsMap.set(key, (deps = new Set()))}// 最后将当前激活的副作用函数添加到bucket中deps.add(activeEffect)return target[key]},set (target, key, newVal) {target[key] = newVal// 获取bucket下对应的数据const depsMap = bucket.get(target)if (!depsMap) return// 根据key 获取副作用的执行函数const effects = depsMap.get(key)// 执行副作用函数effects && effects.forEach(fn => fn())}
})effect(() => {document.body.innerText = obj.text
})
effect(() => {document.title = obj.a
})

8.完全实现——我的测试

 const data = { text: '这是obj.title', a:'vue响应式数据的实现原理' }// 存储副作用函数的桶const bucket = new Map()// 存储副作用函数的变量let activeEffect;// 对数据进行代理const obj = new Proxy(data, {get(target, key) {// 设置副作用函数到map数据的桶中// 判断不存在activeEffect直接返回if (!activeEffect) return target[key];// 存在activeEffect时将副作用函数设置到桶中let targetMap = bucket.get(target); //target对象存在才能判断key是否存在(targetMap既是bucket的值targetMap = new Map()又是keyMap的键的定义)if (!targetMap) {bucket.set(target, (targetMap = new Map()));}let keyMap = targetMap.get(key); // keyMap既是targetMap的值又是if (!keyMap) {targetMap.set(key, (keyMap = new Set()))}keyMap.add(activeEffect); //副作用函数最终是存在Set结构里面的return target[key]},set(target, key, newVal) {target[key] = newVal;//获取桶里面的对象Map的key的map集合的值,即所有的副作用函数进行循环执行let targetMap = bucket.get(target);if (!targetMap) return;let effects = targetMap.get(key);effects.forEach(fn => fn())return true}})function effect(fn) {// 将函数赋值给activeEffect,数据劫持到activeEffect有值时,会将其设置到bucket存储副作用的桶中,当拦截到数据更改时再获取对应函数并执行activeEffect = fn;fn();}// 副作用函数执行一次effect(() => {document.body.innerText = obj.text})effect(() => {document.title = obj.a})

http://www.lryc.cn/news/207839.html

相关文章:

  • 内核进程的调度与进程切换
  • docker-rabbitmq 安装依赖
  • (1)(1.9) HC-SR04声纳
  • 06 MIT线性代数-列空间和零空间 Column space Nullspace
  • 【每日一题Day360】LC1465切割后面积最大的蛋糕 | 贪心
  • 中国地名信息库
  • 网络时代下的声音之路:如何在中央新闻媒体发布网评稿
  • Selenium中WebDriver最新Chrome驱动安装教程
  • 云原生Docker数据管理
  • endnote设置
  • 计算机网络整理-简称缩写【期末复习|考研复习】
  • Flink Hive Catalog操作案例
  • NSSCTF做题第9页(3)
  • 从瀑布模式到水母模式:ChatGPT如何赋能软件研发全流程【文末送书五本】
  • 设置使用LibreOffice作为默认程序打开word、excel等文档
  • 创新领航 | 竹云参编《基于区块链的数据资产评估实施指南》正式发布!
  • 【Docker】Linux网桥连接多个命名空间
  • ES6新特性:let关键字详解
  • 鸿运主动安全监控云平台任意文件下载漏洞复现 [附POC]
  • 使用pycharm远程连接到Linux服务器进行开发
  • JavaScript 中 BOM 基础知识有哪些?
  • 【PointNet—论文笔记分享】
  • Mysql8.1.0 windows 绿色版安装
  • 何为自制力?如何提高自制力?
  • 第1篇 目标检测概述 —(3)目标检测评价指标
  • 剑指JUC原理-3.线程常用方法及状态
  • MYSQL8-sql语句使用集合。MYCAT-sql语法使用集合
  • UNIX 域协议(本地通信协议)
  • 分类预测 | MATLAB实现SSA-CNN-BiGRU-Attention数据分类预测(SE注意力机制)
  • 基于FPGA的图像PSNR质量评估计算实现,包含testbench和MATLAB辅助验证程序