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


    1.命令式和声明式框架

    命令式框架关注过程

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

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

    1. <body>
    2. <div id="app">
    3. <input type="text" />
    4. <h1>h1>
    5. <button>按钮button>
    6. div>
    7. body>
    8. <script>
    9. // vue2实现双绑
    10. const input = document.getElementsByTagName('input')[0]
    11. const h1 = document.getElementsByTagName('h1')[0]
    12. const btn = document.getElementsByTagName('button')[0]
    13. let data = { text: '' }
    14. // input框输入数据,h1数据和text一致;实现点击按钮时,h1 标签数据和input框数据同时更改
    15. Object.defineProperty(data, 'text', {
    16. get() {
    17. return data['text'];
    18. },
    19. set(value) {
    20. // 获取到值后将h1后的内容设置为text
    21. h1.innerText = value;
    22. input.value = value;
    23. return true;
    24. }
    25. });
    26. input.oninput = function (e) {
    27. data.text = e.target.value;
    28. }
    29. btn.onclick = function () {
    30. data.text = "你好"
    31. }
    32. script>

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

    1. // vue3实现双绑
    2. const input = document.getElementsByTagName('input')[0]
    3. const h1 = document.getElementsByTagName('h1')[0]
    4. const btn = document.getElementsByTagName('button')[0]
    5. let data = { text: '' }
    6. let obj = new Proxy(data, {
    7. get(target, property) {
    8. return target[property]
    9. },
    10. set(target, property, value) {
    11. h1.innerText = value;
    12. input.value = value;
    13. return true;
    14. }
    15. })
    16. input.oninput = function (e) {
    17. obj.text = e.target.value;
    18. }
    19. btn.onclick = function () {
    20. obj.text = "你好"
    21. }

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

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

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

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

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

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

    1. // 初始数据
    2. const data = { text: '' }
    3. // 存储副作用函数的桶
    4. const bucket = new Set()
    5. // 对数据进行代理
    6. const obj = new Proxy(data, {
    7. get(target, key) {
    8. bucket.add(effect)
    9. return target[key]
    10. },
    11. set(target, key, newVal) {
    12. target[key] = newVal
    13. bucket.forEach(fn => fn())
    14. return true
    15. }
    16. })
    17. function effect () {
    18. document.body.innerText = obj.text
    19. }
    20. effect()
    21. setTimeout(() => {
    22. obj.text = '你好'
    23. }, 1000)

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

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

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

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

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

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

    1. let activeEffect
    2. function effect(fn) {
    3. activeEffect = fn
    4. fn()
    5. }
    6. effect(() => {
    7. document.body.innerText = obj.text
    8. })
    9. const obj = new Proxy(data, {
    10. get(target, key) {
    11. if (activeEffect) {
    12. bucket.add(activeEffect)
    13. }
    14. return target[key]
    15. },
    16. set(target, key, newVal) {
    17. target[key] = newVal
    18. bucket.forEach(fn => fn())
    19. return true
    20. }
    21. })

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

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

    解决:

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

    1. const obj = new Proxy(data, {
    2. get(target, key) {
    3. console.log(activeEffect, 'activeEffect')
    4. // 没有副作用函数,容错处理 直接return
    5. if (!activeEffect) return target[key]
    6. // 判断桶中是否已经存在key与target的关联关系
    7. let depsMap = bucket.get(target)
    8. // 创建一个新的Map结构与 target 关联
    9. if (!depsMap) {
    10. bucket.set(target, (depsMap = new Map()))
    11. }
    12. // 判断当前Map数据中是否存在key与effect的关联关系
    13. let deps = depsMap.get(key)
    14. // 不存在的话 则新建一个Set 与 key关联
    15. if (!deps) {
    16. depsMap.set(key, (deps = new Set()))
    17. }
    18. // 最后将当前激活的副作用函数添加到bucket中
    19. deps.add(activeEffect)
    20. return target[key]
    21. },
    22. set (target, key, newVal) {
    23. target[key] = newVal
    24. // 获取bucket下对应的数据
    25. const depsMap = bucket.get(target)
    26. if (!depsMap) return
    27. // 根据key 获取副作用的执行函数
    28. const effects = depsMap.get(key)
    29. // 执行副作用函数
    30. effects && effects.forEach(fn => fn())
    31. }
    32. })
    33. effect(() => {
    34. document.body.innerText = obj.text
    35. })
    36. effect(() => {
    37. document.title = obj.a
    38. })

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

    1. const data = { text: '这是obj.title', a:'vue响应式数据的实现原理' }
    2. // 存储副作用函数的桶
    3. const bucket = new Map()
    4. // 存储副作用函数的变量
    5. let activeEffect;
    6. // 对数据进行代理
    7. const obj = new Proxy(data, {
    8. get(target, key) {
    9. // 设置副作用函数到map数据的桶中
    10. // 判断不存在activeEffect直接返回
    11. if (!activeEffect) return target[key];
    12. // 存在activeEffect时将副作用函数设置到桶中
    13. let targetMap = bucket.get(target); //target对象存在才能判断key是否存在(targetMap既是bucket的值targetMap = new Map()又是keyMap的键的定义)
    14. if (!targetMap) {
    15. bucket.set(target, (targetMap = new Map()));
    16. }
    17. let keyMap = targetMap.get(key); // keyMap既是targetMap的值又是
    18. if (!keyMap) {
    19. targetMap.set(key, (keyMap = new Set()))
    20. }
    21. keyMap.add(activeEffect); //副作用函数最终是存在Set结构里面的
    22. return target[key]
    23. },
    24. set(target, key, newVal) {
    25. target[key] = newVal;
    26. //获取桶里面的对象Map的key的map集合的值,即所有的副作用函数进行循环执行
    27. let targetMap = bucket.get(target);
    28. if (!targetMap) return;
    29. let effects = targetMap.get(key);
    30. effects.forEach(fn => fn())
    31. return true
    32. }
    33. })
    34. function effect(fn) {
    35. // 将函数赋值给activeEffect,数据劫持到activeEffect有值时,会将其设置到bucket存储副作用的桶中,当拦截到数据更改时再获取对应函数并执行
    36. activeEffect = fn;
    37. fn();
    38. }
    39. // 副作用函数执行一次
    40. effect(() => {
    41. document.body.innerText = obj.text
    42. })
    43. effect(() => {
    44. document.title = obj.a
    45. })

  • 相关阅读:
    c#快速获取超大文件夹文件名
    串的概念及操作
    1-1VMware介绍
    基于vue和nodejs的项目知识信息分享平台
    MySQL数据库必会的增删查改操作(CRUD)
    # Java手写LRU缓存算法
    用神经网络表示与逻辑,神经网络实现逻辑运算
    [LeetCode周赛复盘] 第 299 场周赛20220626
    SpringBoot使用EasyExcel类一键导出数据库数据生成Excel,导入Excle生成List<>数据(作者直接给demo项目)
    Flink -- 状态与容错
  • 原文地址:https://blog.csdn.net/qq_34569497/article/details/134060818