Vue核心知识点 -Vue2响应式系统是基于什么实现的、以及会产生什么问题和解决方案

一、概念

        在Vue 2中,响应式系统是基于Object.defineProperty实现的。它通过劫持对象的属性来实现数据的响应式更新。

        当你将一个对象传递给Vue实例的data选项时,Vue会遍历对象的每个属性,并使用Object.defineProperty方法将其转换为getter和setter。这样一来,当你访问或修改这些属性时,Vue能够捕获到这些操作并触发相应的更新。

        具体而言,Object.defineProperty方法用于定义一个对象的新属性,或者修改对象的现有属性。通过在属性上设置getter和setter,我们可以监听属性的读取和修改行为,并在这些行为发生时执行相应的操作。

        Vue的响应式系统利用了这一特性,在getter中收集依赖(即追踪属性的订阅者),在setter中触发更新(通知订阅者进行响应式更新)。

二、产生的问题

        1、Vue的响应式系统仅对已经存在的属性进行劫持,而不会劫持对象的整个原型链。这意味着如果你向一个已经创建的对象直接添加新属性或删除属性,Vue无法检测到这些更改

        2、由于数组本质上是对象,Vue可以通过Object.definePropertyProxy拦截对象的属性访问和修改。但是,对于数组而言,直接修改索引对应的值(如果对应的值是对象数据、指的是修改整条对象数据、即将一整个对象赋值给当前索引的对象数据,修改当前索引值对应的值​​​​​​对象的某个属性是会触发dom更新的)并不会触发属性的setter,因此Vue无法监测到这种变化

三、产生问题的场景和解决方案

场景1:

直接为data里的对象通过赋值的方式添加属性和delete删除属性、控制台打印数据添加和删除属性成功、但是页面未能渲染最新数据、即未触发dom的更新

演示代码

<template>
  <div class="home">
    <h3>这是vue2测试页面</h3>
    <p>用户信息: {{ userMsg }} </p>
    <p><button @click="add">添加用户性别为男</button></p>
    <p><button @click="change">修改用户年龄为18</button></p>
    <p><button @click="deleteH">删除用户身高信息</button></p>
  </div>
</template>

<script>

export default {
  data() {
    return {
      // 用户信息
      userMsg: {
        name: 'zs',
        age: '20',
        height: '180',
      }
    }
  },
  methods: {
    // 为对象直接添加数据
    add() {
      this.userMsg.sex = '男'
      console.log('添加性别后')
      console.log(this.userMsg)
    },
    change() {
      this.userMsg.age = '18'
      console.log('修改数据年龄后')
      console.log(this.userMsg)
    },
    deleteH() {
      delete this.userMsg.height
      console.log('删除身高数据后')
      console.log(this.userMsg)
    }
  },
}
</script>
<style>
  .home{
    margin: 0 auto;
    text-align: center;
  }
</style>

图示

点击了添加用户性别为男按钮-数据已经添加 dom元素未更新

 点击了删除用户身高信息按钮-数据已经修改 dom元素未更新

解决方案

方案一: 浅拷贝要操作对象数据给一个新对象、对新对象操作、然后将新对象赋值给要操作的对象数据。
原理:

        这些操作都是对操作对象进行重新赋值,Vue 可以检测到这种赋值操作,并且会重新渲染相关的 DOM 元素,因此你看到了 DOM 元素能够更新。

代码
<template>
  <div class="home">
    <h3>这是vue2测试页面</h3>
    <p>用户信息: {{ userMsg }} </p>
    <p><button @click="add">添加用户性别为男</button></p>
    <p><button @click="change">修改用户年龄为18</button></p>
    <p><button @click="deleteH">删除用户身高信息</button></p>
  </div>
</template>

<script>

export default {
  data() {
    return {
      // 用户信息
      userMsg: {
        name: 'zs',
        age: '20',
        height: '180',
      }
    }
  },
  methods: {
    // 为对象直接添加数据
    add() {
      // this.userMsg.sex = '男'
      let newUserMsg = { ...this.userMsg }
      newUserMsg.sex = '男'
      this.userMsg = newUserMsg
      console.log('添加性别后')
      console.log(this.userMsg)
    },
    change() {
      this.userMsg.age = '18'
      console.log('修改数据年龄后')
      console.log(this.userMsg)
    },
    deleteH() {
      let newUserMsg = { ...this.userMsg }
      delete newUserMsg.height
      this.userMsg = newUserMsg
      console.log('删除身高数据后')
      console.log(this.userMsg)
    }
  },
}
</script>
<style>
  .home{
    margin: 0 auto;
    text-align: center;
  }
</style>
效果
点击了添加用户性别为男按钮-数据已经添加 dom元素更新

 点击了删除用户身高信息按钮-数据已经修改 dom元素更新
方案二:使用 this.$set(); 来添加对象属性、使用this.$delete();来删除属性。
原理:
  • Vue.set(obj, key, value):将响应式对象 obj 的属性 key 设置为 value,如果 obj 是响应式的,则确保这个属性也是响应式的,并触发视图更新。
  • Vue.delete(obj, key):删除响应式对象 obj 的属性 key,并触发视图更新。
代码
<template>
  <div class="home">
    <h3>这是vue2测试页面</h3>
    <p>用户信息: {{ userMsg }} </p>
    <p><button @click="add">添加用户性别为男</button></p>
    <p><button @click="change">修改用户年龄为18</button></p>
    <p><button @click="deleteH">删除用户身高信息</button></p>
  </div>
</template>

<script>

export default {
  data() {
    return {
      // 用户信息
      userMsg: {
        name: 'zs',
        age: '20',
        height: '180',
      }
    }
  },
  methods: {
    // 为对象直接添加数据
    add() {
      // this.userMsg.sex = '男'
      // let newUserMsg = { ...this.userMsg }
      // newUserMsg.sex = '男'
      // this.userMsg = newUserMsg
      this.$set(this.userMsg, 'sex', '男');
      console.log('添加性别后')
      console.log(this.userMsg)
    },
    change() {
      this.userMsg.age = '18'
      console.log('修改数据年龄后')
      console.log(this.userMsg)
    },
    deleteH() {
      // let newUserMsg = { ...this.userMsg }
      // delete newUserMsg.height
      // this.userMsg = newUserMsg
      this.$delete(this.userMsg, 'height');
      console.log('删除身高数据后')
      console.log(this.userMsg)
    }
  },
}
</script>
<style>
  .home{
    margin: 0 auto;
    text-align: center;
  }
</style>
 效果
点击了添加用户性别为男按钮-数据已经添加 dom元素更新

 点击了删除用户身高信息按钮-数据已经修改 dom元素更新

场景2:

直接为data里的数组通过直接修改索引对应的值,页面未能渲染最新数据、即未触发dom的更新

演示代码

<template>
  <div class="home">
    <h3>这是vue2测试页面</h3>
    <!-- <p>用户信息: {{ userMsg }} </p> -->
    <p>用户家人信息: {{ userMsg.family }} </p>
    <!-- <p><button @click="add">添加用户性别为男</button></p>
    <p><button @click="change">修改用户年龄为18</button></p>
    <p><button @click="deleteH">删除用户身高信息</button></p> -->
    <p><button @click="changeFather">修改父亲信息</button></p>
  </div>
</template>

<script>

export default {
  data() {
    return {
      // 用户信息
      userMsg: {
        name: 'zs',
        age: '20',
        height: '180',
        family: [{ name: 'father', age: '45', sex: '男' }, { name: 'mother', age: '42', sex: '女' }]
      }
    }
  },
  methods: {
    // 为对象直接添加数据
    add() {
      // this.userMsg.sex = '男'
      // let newUserMsg = { ...this.userMsg }
      // newUserMsg.sex = '男'
      // this.userMsg = newUserMsg
      this.$set(this.userMsg, 'sex', '男');
      console.log('添加性别后')
      console.log(this.userMsg)
    },
    change() {
      this.userMsg.age = '18'
      console.log('修改数据年龄后')
      console.log(this.userMsg)
    },
    deleteH() {
      // let newUserMsg = { ...this.userMsg }
      // delete newUserMsg.height
      // this.userMsg = newUserMsg
      this.$delete(this.userMsg, 'height');
      console.log('删除身高数据后')
      console.log(this.userMsg)
    },
    // 修改父亲信息
    changeFather() {
      this.userMsg.family[0] = { name: 'father', age: '40', sex: '男' ,height: '180'}
      // this.userMsg.family[0].age = '40' // dom 是会更新的
      console.log('修改后的父亲信息')
      console.log(this.userMsg.family)
    },
  },
}
</script>
<style>
  .home{
    margin: 0 auto;
    text-align: center;
  }
</style>

图示

点击了修改父亲信息按钮、数据更新成功、但是页面未同步渲染、即为dom未更新

解决方案

和场景一差不多

方案一 浅拷贝数组每条数据到一个新数组 对浅拷贝的数组数据操作 将浅拷贝的数组数据赋值给数组数据

方案二 this.$set() 方法

<template>
  <div class="home">
    <h3>这是vue2测试页面</h3>
    <!-- <p>用户信息: {{ userMsg }} </p> -->
    <p>用户家人信息: {{ userMsg.family }} </p>
    <!-- <p><button @click="add">添加用户性别为男</button></p>
    <p><button @click="change">修改用户年龄为18</button></p>
    <p><button @click="deleteH">删除用户身高信息</button></p> -->
    <p><button @click="changeFather">修改父亲信息</button></p>
  </div>
</template>

<script>

export default {
  data() {
    return {
      // 用户信息
      userMsg: {
        name: 'zs',
        age: '20',
        height: '180',
        family: [{ name: 'father', age: '45', sex: '男' }, { name: 'mother', age: '42', sex: '女' }]
      },
    }
  },
  methods: {
    // 为对象直接添加数据
    add() {
      // this.userMsg.sex = '男'
      // let newUserMsg = { ...this.userMsg }
      // newUserMsg.sex = '男'
      // this.userMsg = newUserMsg
      this.$set(this.userMsg, 'sex', '男');
      console.log('添加性别后')
      console.log(this.userMsg)
    },
    change() {
      this.userMsg.age = '18'
      console.log('修改数据年龄后')
      console.log(this.userMsg)
    },
    deleteH() {
      // let newUserMsg = { ...this.userMsg }
      // delete newUserMsg.height
      // this.userMsg = newUserMsg
      this.$delete(this.userMsg, 'height');
      console.log('删除身高数据后')
      console.log(this.userMsg)
    },
    // 修改父亲信息
    changeFather() {
      // this.userMsg.family[0] = { name: 'father', age: '40', sex: '男' ,height: '180'}
      // this.userMsg.family[0].age = '40' // dom 是会更新的

      // 方案一 浅拷贝数组每条数据到一个新数组 对浅拷贝的数组数据操作 将浅拷贝的数组数据赋值给数组数据
      let newArr = [ ...this.userMsg.family ]
      newArr[0] = { name: 'father', age: '40', sex: '男' ,height: '180'}
      this.userMsg.family = newArr
      console.log(this.userMsg)

      // 方案二 this.$set() 方法
      // this.$set(this.userMsg.family, 0, { name: 'father', age: '40', sex: '男' });
    },
  },
}
</script>
<style>
  .home{
    margin: 0 auto;
    text-align: center;
  }
</style>

四、简化版的数据劫持源码

// 定义一个构造函数 Dep,用于收集依赖和通知更新
function Dep() {
  this.subs = [];
}

Dep.prototype = {
  addSub(sub) {
    this.subs.push(sub);
  },
  notify() {
    this.subs.forEach(sub => {
      sub.update();
    });
  }
};

// 定义一个监听器 Observer,用于劫持对象的属性
function Observer(data) {
  this.data = data;
  this.walk(data);
}

Observer.prototype = {
  walk(data) {
    Object.keys(data).forEach(key => {
      this.defineReactive(data, key, data[key]);
    });
  },
  defineReactive(data, key, val) {
    const dep = new Dep();
    Object.defineProperty(data, key, {
      enumerable: true,
      configurable: true,
      get() {
        if (Dep.target) {
          dep.addSub(Dep.target);
        }
        return val;
      },
      set(newVal) {
        if (val === newVal) {
          return;
        }
        val = newVal;
        dep.notify();
      }
    });
  }
};

// 定义一个 Watcher,用于进行依赖收集和更新
function Watcher(vm, exp, cb) {
  this.vm = vm;
  this.exp = exp;
  this.cb = cb;
  this.value = this.get();
}

Watcher.prototype = {
  get() {
    Dep.target = this;
    const value = this.vm[this.exp];
    Dep.target = null;
    return value;
  },
  update() {
    const value = this.vm[this.exp];
    this.cb.call(this.vm, value);
  }
};

// 宮户 Vue 构造函数
function Vue(options) {
  this.data = options.data;
  new Observer(this.data);
  // 初始化一个 Watcher 对象,用于触发依赖收集
  new Watcher(this, 'data', () => {
    console.log('数据更新了');
  });

  // 其他 Vue 相关逻辑...
}

// 创建一个 Vue 实例
var vm = new Vue({
  data: {
    message: 'Hello, Vue!'
  }
});

// 修改数据,触发更新
vm.data.message = 'Hello, New Vue!';

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

YOLO_you only look once

前言 计算机图形学的课程即将结束&#xff0c;我需要提交一份关于YOLO模型的学习报告。在这段时间里&#xff0c;我对YOLO进行了深入的学习和研究&#xff0c;并记录下了我的学习过程和心得体会。本文将详细介绍YOLO模型的原理、优缺点以及应用领域&#xff0c;希望能够为后续…

nodejs pkg打包跨平台执行文件,带.node插件(sharp、sqlite3)

在nodejs引入的第三方库中,大部分插件都是nodejs原生开发,使用pkg可以快速打包,生成windows、linux(ubuntu、centOS等)、麒麟系统下面执行文件。遇到了第三方插件gdal、sharp、sqlite3,在webstorm中打包生成执行文件,跨平台部署的时候会出现找不到###.node文件,需要获取部…

多源BFS - 01矩阵

LCR 107. 01 矩阵 到最近的0的距离&#xff0c;对每一个非0的位置进行搜索&#xff0c;找到最短的距离即可&#xff0c;但如果对每一个非0的点都进行一次搜索的话&#xff0c;肯定是会超时的。这里可以考虑&#xff0c;将所有0点想象成一个0点(超级0)。然后找到所有1点到超级0的…

基于ssm的旅游管理系统

技术&#xff1a;ssmmysqljsp 一、背景 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。所以各行业&#xff0c;尤其是规模较大…

Nginx鉴权、限流

文章目录 一、Nginx鉴权1. 依赖模块2. Nginx配置3. Rest接口 二、Nginx限流1. 简介2. 控制速率2. 控制连接数 一、Nginx鉴权 1. 依赖模块 依赖模块 http_auth_request_module验证是否安装 nginx -V 2>&1 | grep -- http_auth_request_module2. Nginx配置 server {li…

Python模块-基础知识

Python模块-基础知识 1.模块分类&#xff1a; &#xff08;1&#xff09;自定义模块&#xff1a; 如果你自己写一个py文件&#xff0c;在文件内写入一堆函数&#xff0c;则它被称为自定义模块&#xff0c;即使用python编写的.py文件 &#xff08;2&#xff09;第三方模块&…

函数栈帧的创建和销毁 - 局部变量|函数传参|函数调用|函数返回|图文详解

目录 1.寄存器EBP和ESP 2.函数栈帧的创建 3.函数的调用 4. 函数栈帧的销毁 函数栈帧&#xff08;function stack frame&#xff09;是在函数调用期间在栈上分配的内存区域&#xff0c;用于存储函数的局部变量、参数、以及用于函数调用和返回的相关信息。每当函数被调用时&a…

品牌方年度抖音店铺打造流量运营孵化方案

【干货资料持续更新&#xff0c;以防走丢】 品牌方年度抖音店铺打造流量运营孵化方案 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 PDF共120页&#xff08;完整资料包含以下内容&#xff09; 目录 抖音年度短视频直播运营规划方案 1. 帐号视频发布规划 问…

C++进阶:二叉搜索树介绍、模拟实现(递归迭代两版本)及其应用

上次介绍完多态后&#xff1a;C进阶&#xff1a;详解多态&#xff08;多态、虚函数、抽象类以及虚函数原理详解&#xff09; 也是要开始继续学习了 文章目录 1.二叉搜索树1.1概念1.2二叉搜索树特性1.3 二叉搜索树的操作 2.模拟实现2.1项目文件规划2.2基本结构2.3各种接口、功能…

【数值模型系列】模拟区域网格设置工具WRFDomainWizard网页版使用介绍

大气数值模型首先需要进行模拟区域参数设置&#xff0c;这一过程可以使用WRF官网提供的WRFDomainWizard软件工具&#xff0c;也可以使用QGIS/GIS4WRF&#xff0c;甚至可以手动设置多次调整&#xff08;不推荐但不少人使用&#xff09;。本文介绍WRFDomainWizard工具的网页版&am…

六、循环结构

在python当中有两类循环结构&#xff1a;for循环和while循环 一、遍历for循环 for循环首先判断遍历对象中是否有元素&#xff0c;在依次遍历 for循环常与range&#xff08;&#xff09;函数使用 for i in range(1,10,):#range()函数依次遍历1~10但不包括10print(i,end ) p…

《尚品甄选》:后台系统——通过面向切面编程AOP,实现记录日志功能

文章目录 一、记录日志的意义二、日志数据表结构三、记录日志思想四、切面类环境搭建五、保存日志数据 一、记录日志的意义 后台管理系统记录操作日志的意义非常重要&#xff0c;主要体现在以下几个方面&#xff1a; 安全性&#xff1a;操作日志可以记录管理员操作行为&#…

【每日一题】数组元素的最小非零乘积

文章目录 Tag题目来源解题思路方法一&#xff1a;贪心 写在最后 Tag 【贪心】【快速幂】【数组】【2024-03-20】 题目来源 1969. 数组元素的最小非零乘积 解题思路 方法一&#xff1a;贪心 前言 我们首先来思考一个简单的问题&#xff1a;假设给定三个整数 a a a&#xf…

JMeter 并发测试和持续性压测详解

并发测试和持续性压测都是评估系统性能的常用方法&#xff0c;它们可以帮助开发人员发现并解决系统中的性能问题。本文来详细介绍下。 概念 并发测试&#xff1a; 旨在评估系统在同时处理多个用户请求时的性能。在这种 测试 中&#xff0c;系统会暴露于一定数量的用户负载下&…

CodeWhisperer插件

一、前言 产品官网地址&#xff1a;What is CodeWhisperer? - CodeWhisperer Amazon CodeWhisperer 是一个通用的、由机器学习驱动的代码生成器&#xff0c;可实时为您提供代码建议。在您编写代码时&#xff0c;CodeWhisperer 会根据您现有的代码和注释自动生成建议。您的个…

一招让你的Mac重获新生,CleanMyMac助你轻松清理无用垃圾!

一招让你的Mac重获新生&#xff0c;CleanMyMac助你轻松清理无用垃圾&#xff01; 告别卡顿&#xff0c;让你的Mac跑得更快更稳&#xff01; 在当今这个快节奏的生活中&#xff0c;我们的工作和生活早已离不开电脑。特别是对于Mac用户来说&#xff0c;一台轻巧、快捷、稳定的Mac…

代码随想录算法训练营Day51 ||leetCode 309.最佳买卖股票时机含冷冻期 || 714.买卖股票的最佳时机含手续费

309.最佳买卖股票时机含冷冻期 需要新添加状态 class Solution { public:int maxProfit(vector<int>& prices) {int n prices.size();if (n 0) return 0;vector<vector<int>> dp(n, vector<int>(4, 0));dp[0][0] - prices[0]; // 持股票for (i…

[.NET项目实战] Elsa开源工作流组件应用(三):实战演练

补充 之前的文章简单介绍了工作流和Elsa工作流库&#xff0c;这里再补充说明两点 工作流的使用场景非常广泛&#xff0c;几乎涵盖了所有需要进行业务流程自动化管理的领域。 学习一个开源库&#xff0c;最简单的方法就是看源码&#xff0c;Elsa的工作流引擎源码非常简单易懂&…

【Flutter 面试题】讲一讲 Dart 的一些重要概念?

【Flutter 面试题】讲一讲 Dart 的一些重要概念&#xff1f; 文章目录 写在前面口述回答补充说明完整代码运行结果详细说明 写在前面 &#x1f64b; 关于我 &#xff0c;小雨青年 &#x1f449; CSDN博客专家&#xff0c;GitChat专栏作者&#xff0c;阿里云社区专家博主&#…

【Leetcode每日一题】 递归 - 反转链表(难度⭐)(36)

1. 题目解析 题目链接&#xff1a;206. 反转链表 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 一、递归函数的核心任务 递归函数的主要职责是接受一个链表的头指针&#xff0c;并返回该链表逆序后的新头结点。递归…