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

news2025/2/25 9:45:23

一、概念

        在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!';

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

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

相关文章

项目总结报告-word

2 项目工作成果 2.1 交付给用户的产品 2.2 交付给研发中心的产品 2.2.1 代码部分 2.2.2 文档部分 2.3 需求完成情况与功能及性能符合性统计 2.3.1 需求完成情况统计 2.3.2 功能符合性分析 2.3.3 性能符合性分析 3 项目工作分析 3.1 项目计划与进度实施分析 3.1.1 开发进度 3.1.…

2000-2021年各省研发强度数据(原始数据+计算结果)(无缺失)

2000-2021年各省研发强度数据&#xff08;原始数据计算结果&#xff09;&#xff08;无缺失&#xff09; 1、时间&#xff1a;2000-2021年 2、指标&#xff1a;RD经费内部支出&#xff08;万元&#xff09;、国内生产总值、研发强度 3、范围&#xff1a;31省 4、来源&#…

【源码阅读】EVMⅢ

参考[link](https://blog.csdn.net/weixin_43563956/article/details/127725385 大致流程如下&#xff1a; 编写合约 > 生成abi > 解析abi得出指令集 > 指令通过opcode来映射成操作码集 > 生成一个operation 以太坊虚拟机的工作流程&#xff1a; 由solidity语言编…

数据库系统概论-练手题集合【期末复习|考研复习】

前言 总结整理不易&#xff0c;希望大家点赞收藏。 给大家整理了一下数据库系统概论中的练手题&#xff0c;以供大家期末复习和考研复习的时候使用。 数据库系统概论系列文章传送门&#xff1a; 第一章 绪论 第二/三章 关系数据库和标准语言SQL 第四/五章 数据库安全性和完整性…

linux:线程互斥

个人主页 &#xff1a; 个人主页 个人专栏 &#xff1a; 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、线程互斥问题解释互斥量的接口 二、加锁的原理三、 死锁死锁四个必要条件避免死锁 总结 前言 本文是对于线程互斥的知识总结 一、线程互斥 问题 我们先看下面…

Division by Invariant Integers using Multiplication

在处理器中&#xff0c;整数除法的成本通常是整数乘法的几倍&#xff1a; 流水线式的组合乘法器通常在不到10个周期内完成操作&#xff1b;而对于整数除法则没有硬件支持&#xff0c;或者使用的迭代除法器比乘法器慢几倍。 表 1.1 比较了一些处理器上乘法和除法的时间。这张表…

c++多长时间会被Python或者其他语言取代?

c多长时间会被Python或者其他语言取代&#xff1f; 如果不考虑市场因素&#xff0c;C#今天就可以取代C。 自.NET跨平台至今&#xff0c;C能做的工作&#xff0c;C#都能做了&#xff0c;且性能差别不大。 在C最有优势的嵌入式UI方面&#xff0c;C#可以拿出Avalonia替代QT。用 …

存储器的层次结构和局部性原理

前言 大家好我是jiantaoyab&#xff0c;这是我所总结作为学习的笔记第19篇&#xff0c;在这里分享给大家&#xff0c;这篇文章讲存储器的一部分内容。 存储器的层次结构 SRAM 静态随机存取存储器的芯片&#xff0c;SRAM 之所以被称为“静态”存储器&#xff0c;是因为只要处…

MYSQL概念和编译安装

目录 一、数据库概述 1.1数据 1.2表 1.3数据库 总结&#xff1a; 2.数据库管理系统&#xff08;DBMS&#xff09; 3.DBMS工作模式 4.数据库系统原理 二、数据库发展史 三、主流数据库 四、关系型数据库和非关系型数据库 1.关系型数据库 2.非关系数据库 MYSQL数据…

输出菱形(*)--c语言

//输出菱形 #include<stdio.h>int main(){//上int line0;scanf("%d",&line);int i0;for(i0;i<line;i){int j0;//输出空格for(j0;j<line-1-i;j){printf(" ");}//输出*号for(j0;j<2*i1;j){printf("*");}printf("\n")…

Redisson 分布式锁原理分析

Redisson 分布式锁原理分析 示例程序 示例程序&#xff1a; public class RedissonTest {public static void main(String[] args) {Config config new Config();config.useSingleServer().setPassword("123456").setAddress("redis://127.0.0.1:6379"…

【开发环境】Ubuntu 18.04 搭建 QT编译环境详细步骤 【亲测有效】

目录 1 查看Ubuntu系统中Qt版本 2 下载Ubuntu系统Qt版本安装包 3 Qt安装 3.1 Qt 安装步骤 3.2 安装qt发现Ubuntu空间不足&#xff0c;怎么去扩容呢&#xff1f; 3.2.1 硬盘操作步骤&#xff08;需要关闭虚拟机进行操作&#xff09; 3.2.2 Ubuntu命令操作&#xff1a;安装…

基于单片机的模糊PID炉温控制系统设计

摘 要 电热炉是在工业热处理的生产中广泛使用的一种设备&#xff0c;电热炉的温度控制系统存在时变性&#xff0c;非线性&#xff0c;滞后性等特征&#xff0c;难以用常规PID的控制器对系统达到很好的控制效果。当控温精度的要求高时&#xff0c;使用传统的控制理论方法难以达…

蓝桥杯刷题|03普及-真题

[蓝桥杯 2017 省 B] k 倍区间 题目描述 给定一个长度为 N 的数列&#xff0c;​,,⋯&#xff0c;如果其中一段连续的子序列 ​,,⋯ (i≤j) 之和是 K 的倍数&#xff0c;我们就称这个区间 [i,j] 是 K 倍区间。 你能求出数列中总共有多少个 K 倍区间吗&#xff1f; 输入格式 …

微服务高级篇(一):微服务保护+Sentinel

文章目录 一、初识Sentinel1.1 雪崩问题及解决方案1.2 微服务保护技术对比1.3 Sentinel介绍与安装1.4 微服务整合Sentinel 二、Sentinel的流量控制三、Sentinel的隔离与降级四、Sentinel的授权规则五、规则持久化5.1 规则管理模式【原始模式、pull模式、push模式】5.2 实现push…

第二十六节 Java 重写(Override)与重载(Overload)

重写 (Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写&#xff01;返回值和形参都不能改变。即外壳不变&#xff0c;核心重写&#xff01; 重写的好处在于子类可以根据需要&#xff0c;定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。…

面试算法-48-二叉树的锯齿形层序遍历

题目 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,…

从Excel到山海鲸:我的数据可视化升级之旅

作为一名新用户&#xff0c;我最近有幸体验了山海鲸可视化软件&#xff0c;近期山海鲸可视化产品开放了可视化编辑全部功能&#xff0c;并支持本地化部署功能&#xff0c;在使用过程中它不仅打开了我对数据可视化全新世界的大门&#xff0c;而且在实际操作中为我带来了不少惊喜…

【C语言】数据在内存中的存储(包含大小端字节序问题)~

一、前言 我们在刚开始学习C语言的时候&#xff0c;就接触到了很多数据的不同类型。我们也知道&#xff0c;数据是存储在一块内存空间的&#xff0c;且我们只知道数据的类型决定着&#xff0c;该数据在内存中所占内存空间的大小&#xff0c;且超过一个字节的数据在内存中存储的…