vue2响应式系统是如何实现的(手写)

news/2024/9/19 11:18:03/ 标签: vue.js

引言

喜欢请点赞,支持点在看。 关注牛马圈,干货不间断。

忆往昔

回头看,已经使用vue2多年,虽然也掌握了其他几种前端框架,但对vue2是真爱。

核心概念

  1. Object.defineProperty Vue 2的响应式系统核心是使用了Object.defineProperty来劫持(或观察)每个组件的data对象的属性。Object.defineProperty可以给对象的属性添加getter和setter,从而实现对数据的读取和写入进行监听。

  2. 依赖收集(Dependency Collection) 当组件进行渲染时,Vue会记录所有被访问的属性,这些属性被称为“依赖”。每个属性都有一个或多个“观察者”(Watcher),当属性值发生变化时,Vue会通知所有依赖于该属性的观察者。

  3. 观察者(Watcher) 观察者是Vue响应式系统中用于更新视图的机制。当依赖的属性值发生变化时,观察者会被通知,并执行一个函数来更新DOM。

实现步骤

  1. 步骤一:数据劫持(Data Hijacking)
function defineReactive(obj, key, val{
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    enumerabletrue,
    configurabletrue,
    getfunction reactiveGetter({
      if (Dep.target) {
        dep.depend();
      }
      return val;
    },
    setfunction reactiveSetter(newVal{
      if (val === newVal) return;
      val = newVal;
      dep.notify();
    }
  });
}
  1. 步骤二:依赖收集
class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  removeSub(sub) {
    remove(this.subs, sub);
  }
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  }
  notify() {
    const subs = this.subs.slice();
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  }
}
Dep.target = null;
  1. 步骤三:观察者(Watcher)
class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    this.cb = cb;
    this.expOrFn = expOrFn;
    this.depIds = new Set();
    this.value = this.get();
  }
  get() {
    Dep.target = this;
    const value = this.vm._data[this.expOrFn];
    Dep.target = null;
    return value;
  }
  addDep(dep) {
    const id = dep.id;
    if (!this.depIds.has(id)) {
      this.depIds.add(id);
      dep.addSub(this);
    }
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    this.cb.call(this.vm, this.value, oldValue);
  }
}
  1. 步骤四:观察整个数据对象
function observe(value{
  if (!value || typeof value !== 'object') {
    return;
  }
  Object.keys(value).forEach(key => {
    defineReactive(value, key, value[key]);
  });
}
  1. 步骤五:将数据对象转换为响应式
function Vue(options{
  this._data = options.data;
  observe(this._data);
}

局限性

  1. 对象属性的响应性
  • 新增属性:Vue不能检测对象属性的添加或删除。必须使用 Vue.set方法来确保新属性是响应式的,或者通过替换整个对象来触发更新。
  • 删除属性:同样地,使用 Vue.delete来删除属性以确保视图更新。
  1. 数组的响应性
  • 索引赋值:Vue不能检测通过索引直接设置数组项的操作,例如 vm.items[indexOfItem] = newValue
  • 长度修改:Vue不能检测通过修改数组长度的操作,例如 vm.items.length = newLength
  • 解决方法:使用 Vue.set来更新数组项,或者使用数组的 splice方法。
  1. 对象的响应性
  • 直接替换:直接替换一个对象或数组,例如 vm.myObject = newObject,Vue将无法保持原有对象属性的响应性。必须用新对象与旧对象的属性逐一赋值来保持响应性。
  1. 嵌套对象/数组
  • 深层响应性:Vue的响应式系统可以自动侦测嵌套对象或数组的变化,但如果数据结构非常深,性能可能会受到影响。
  1. 异步更新队列
  • DOM更新时机:Vue在观察到数据变化时并不会立即更新DOM,而是开启一个队列,并缓冲在同一个事件循环中发生的所有数据改变。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在异步队列中批量更新的行为可能会导致我们无法立即看到数据变化后的结果。
  1. Object.defineProperty的限制
  • 仅限属性:Vue的响应式系统基于 Object.defineProperty,这意味着它只能侦测属性的变化,而不是对象或数组的变化。
  • **无法侦测到ES6的 MapSet**: Object.defineProperty无法侦测到 MapSet这类数据结构的变化。
  1. 性能开销
  • 大量数据:对于拥有大量数据的对象,每个属性都通过 Object.defineProperty进行代理,可能会导致性能问题。
  1. 不可响应的数据类型
  • 原始类型:基本数据类型(如字符串、数字、布尔值)是响应式的,但它们是不可变的,这意味着你不能通过直接修改它们来触发更新。
  • 冻结对象:被 Object.freeze()冻结的对象无法被设置为响应式。
  1. 计算属性和侦听器
  • 计算属性:计算属性依赖于其响应式依赖项。如果依赖项未发生变化,计算属性不会重新计算。
  • 侦听器:侦听器可以观察数据变化,但过度使用可能导致性能问题。

为了解决这些限制,Vue 3引入了基于Proxy的响应式系统,它解决了上述许多问题,例如对属性的动态添加和删除、更好的性能以及原生支持MapSet等。

vue3虽好,但唯独钟情于vue2,我是不是有点守旧

本文由 mdnice 多平台发布


http://www.ppmy.cn/news/1525602.html

相关文章

深入理解java并发编程之aqs框架

跟synchronized 相比较&#xff0c;可重入锁ReentrankLock其实原理有什么不同&#xff1f; 所得基本原理是为了达到一个目的&#xff1b;就是让所有线程都能看到某种标记。synchronized通过在对象头中设置标记实现了这一目的&#xff0c;是一种JVM原生的锁实现方式。而Reentran…

​经​纬​恒​润​二​面​​三​七​互​娱​一​面​​元​象​二​面​

1. 请尽可能详细地说明&#xff0c;进程和线程的区别&#xff0c;分别有哪些应用场景&#xff1f;进程间如何通信&#xff1f;线程间如何通信&#xff1f;你的回答中不要写出示例代码。 进程和线程是操作系统中的两个基本概念&#xff0c;它们在计算机系统中扮演着不同的角色&…

《数据结构(C语言版)第二版》第八章-排序(8.5-归并排序、8.6基数排序、8.7 外部排序)

8.5 归并排序 (Merging Sort) 【基本思想】 将两个或两个以上的有序表合并成一个有序表的过程。 将两个有序表合并成一个有序表的过程称为2-路归并&#xff0c;2-路归并最为简单和常用。 假设初始序列含有n个记录&#xff0c;则可看成是n个有序的子序列&#xff0c;每个子序列…

Git换行符自动转换参数core.autocrlf的用法

core.autocrlf 是 Git 中用于控制换行符自动转换的配置选项。它有以下几个可能的值&#xff1a; 1. true 作用&#xff1a;在 checkin 时将 CRLF 转换为 LF&#xff0c;在 checkout 时将 LF 转换为 CRLF。适用场景&#xff1a;适用于 Windows 用户&#xff0c;希望在本地文件…

如何让Windows控制台窗口不接受鼠标点击(禁用鼠标输入)

一、简述 在我们编写控制台应用程序时&#xff0c;默认情况下程序的打印输出会在控制台窗口中进行显示&#xff0c;我们在写服务功能时在窗口中会不断打印消息输出&#xff0c;这个时候如果使用鼠标点击了控制台窗口&#xff0c;会阻塞程序的继续运行&#xff0c;导致我们的程…

Python安装:Mac 使用brew 安装Python2 和 Python3

安装python ## python2 brew install python ## python3 brew install python3出现错误 Error: An unexpected error occurred during the brew link step The formula built, but is not symlinked into /usr/local Permission denied dir_s_mkdir - /usr/local/Frameworks …

uniapp媒体

uni.previewImage实现图片放大预览 // 图片预览函数function onPreview(index) {// 收集所有图片的urlvar urls pets.value.data.map(item > item.url)// 预览图片uni.previewImage({current: index, // 当前预览的图片索引urls: urls // 所有图片的url数组})}

HarmonyOS】ArkTS学习之基于TextTimer的简易计时器的elapsedTime最小时间单位问题

本文旨在纪录自己对TextTimer使用过程的疑惑问题 我在查看教程时候&#xff0c;发现很多博客在onTimer(event: (utc: number, elapsedTime: number) > void) 这里提到elapsedTime&#xff1a;计时器经过的时间&#xff0c;单位为毫秒。我不清楚是否为版本问题。 在我查看ver…

编写XBOX控制器实现鼠标键盘输入

1.核心部分, XINPUT输入封装 XInput封装https://mp.csdn.net/mp_blog/creation/editor/1420701282.对话框窗口编写 Win32 对话框封装-CSDN博客https://blog.csdn.net/Flame_Cyclone/article/details/142110008?spm1001.2014.3001.5501 3.使用到的其他封装 字符串编码转换与…

Azure web app has no access to openai private endpoint in virtual network

题意&#xff1a;"Azure Web 应用无法访问虚拟网络中的 OpenAI 私有端点。" 问题背景&#xff1a; I am trying to host a web application similar to a private ChatGPT instance within a secluded virtual network, ensuring that theres no external internet …

以太网--TCP/IP协议(二)

上文中讲述了IP协议&#xff0c;本文主要来讲一下TCP协议。 TCP协议 &#xff08;1&#xff09;端到端通信 直接把源主机应用程序产生的数据传输到目的主机使用这 些数据的应用程序中&#xff0c;就是端到端通信。 &#xff08;2&#xff09;传输层端口 公认端口&#xff0…

CCF刷题计划——训练计划(反向拓扑排序)

训练计划 计算机软件能力认证考试系统 这道题70分还是很好拿的。后面30分需要用到 反向拓扑排序 &#xff0c;相对而言就麻烦点&#xff0c;需要逆序遍历。不着急&#xff0c;我们慢慢来。首先给出70分的代码。 本题可以学到&#xff1a;反向拓扑排序 70分题解&#xff1a;…

红黑树的删除

文章目录 前言一.删除的节点左子树右子树都有二.删除的节点只有左/右子树删除调整操作 三.删除的节点没有孩子1.删除的节点为红色2.删除的节点为黑色1).兄弟节点为黑色(1).兄弟节点至少有一个红色的孩子节点LL型RR型RL型LR型 (2).兄弟节点没有孩子或所有孩子为黑色 2).兄弟节点…

【贪心算法】贪心算法

贪心算法简介 1.什么是贪心算法2.贪心算法的特点3.学习贪心的方向 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.什么是贪心算法 与其说是…

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中&#xff0c;不同电脑的配置和操作系统&#xff08;如Win11与Win7&#xff09;可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行&#xff0c;需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下&a…

Integer 缓存

在 Java 中&#xff0c;如果你通过 new Integer(value) 显式创建一个 Integer 对象&#xff0c;以下几点需要注意&#xff1a; 内存中的 Integer 对象 缓存范围&#xff1a; Java 自动缓存的 Integer 对象范围是从 -128 到 127。这些对象在类加载时被创建并存储在内存中。 使…

服务网关工作原理,如何获取用户真实IP?

文章目录 一、什么是网关二、网关工作原理 (★)三、SpringCloud Gateway3.1 Gateway 简介3.2 Gateway 环境搭建3.3 自定义路由规则 (★)3.4 局部过滤器3.5 全局过滤器&#xff08;案例&#xff1a;获取用户真实IP地址&#xff09; (★) 补充1&#xff1a;不同类型的客户端如何设…

使用@test-library/react的screen中的方法和直接使用getByText,getByTestId等的区别?

在 React Testing Library 中&#xff0c;screen 对象和直接使用 getByText, getByTestId 等方法之间的主要区别在于它们的使用方式和上下文。然而&#xff0c;从功能的角度来看&#xff0c;它们实际上是相互关联的&#xff0c;因为 screen 对象提供了一组封装好的查询方法&…

2024非常全的接口测试面试题及参考答案

一、前言 接口测试最近几年被炒的火热了&#xff0c;越来越多的测试同行意识到接口测试的重要性。接口测试为什么会如此重要呢&#xff1f; 主要是平常的功能点点点&#xff0c;大家水平都一样&#xff0c;是个人都能点&#xff0c;面试时候如果问你平常在公司怎么测试的&…

11Python的Pandas:可视化

Pandas本身并没有直接的可视化功能&#xff0c;但它与其他Python库&#xff08;如Matplotlib和Seaborn&#xff09;无缝集成&#xff0c;允许你快速创建各种图表和可视化。这里是一些使用Pandas数据进行可视化的常见方法&#xff1a; 1. 使用Matplotlib Pandas中的plot()方法…