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

【结合vue源码,分析vue2及vue3的数据绑定实现原理】

结合vue源码,分析vue2及vue3的数据绑定实现原理

      • Vue 2 数据绑定实现
        • 整体思路
        • 详细实现
          • 1. `Observer` 类:数据劫持
          • 2. `Dep` 类:依赖收集
          • 3. `Watcher` 类:订阅者
      • Vue 3 数据绑定实现
        • 整体思路
        • 详细实现
          • 1. `reactive` 函数:创建响应式对象
          • 2. 依赖收集和触发更新
          • 3. `effect` 函数:副作用收集
      • 总结

Vue 2 数据绑定实现

整体思路

Vue 2 的数据绑定主要基于 Object.defineProperty() 方法来实现数据劫持,配合发布 - 订阅模式,当数据发生变化时通知视图更新。主要涉及三个核心部分:Observer 用于数据劫持、Dep 进行依赖收集、Watcher 作为订阅者接收更新通知。
在这里插入图片描述

详细实现
1. Observer 类:数据劫持
// 定义 Observer 类,用于对对象属性进行劫持
class Observer {constructor(data) {// 遍历对象属性this.walk(data);}walk(data) {if (!data || typeof data!== 'object') {return;}Object.keys(data).forEach(key => {this.defineReactive(data, key, data[key]);});}defineReactive(obj, key, val) {// 创建一个 Dep 实例用于依赖收集const dep = new Dep();// 递归处理子对象new Observer(val);Object.defineProperty(obj, key, {enumerable: true,configurable: true,get() {// 收集依赖if (Dep.target) {dep.depend();}return val;},set(newVal) {if (newVal === val) {return;}val = newVal;// 对新值进行递归处理new Observer(newVal);// 通知依赖更新dep.notify();}});}
}

Observer 类会遍历对象的所有属性,使用 Object.defineProperty() 重写属性的 gettersetter。在 getter 中收集依赖,在 setter 中通知依赖更新。

2. Dep 类:依赖收集
// Dep 类用于管理依赖
class Dep {constructor() {// 存储依赖的数组this.subs = [];}depend() {if (Dep.target) {Dep.target.addDep(this);}}addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());}
}
Dep.target = null;

Dep 类有一个 subs 数组来存储依赖(Watcher 实例)。depend 方法用于收集依赖,notify 方法用于通知所有依赖更新。

3. Watcher 类:订阅者
// Watcher 类作为订阅者,监听数据变化
class Watcher {constructor(vm, expOrFn, cb) {this.vm = vm;this.cb = cb;this.getter = parsePath(expOrFn);this.value = this.get();}get() {Dep.target = this;const value = this.getter.call(this.vm, this.vm);Dep.target = null;return value;}addDep(dep) {dep.addSub(this);}update() {const oldValue = this.value;this.value = this.get();this.cb.call(this.vm, this.value, oldValue);}
}function parsePath(path) {const segments = path.split('.');return function (obj) {for (let i = 0; i < segments.length; i++) {if (!obj) return;obj = obj[segments[i]];}return obj;};
}

Watcher 类在实例化时会触发 get 方法,从而触发 getter 进行依赖收集。当数据变化时,Dep 会调用 Watcherupdate 方法,update 方法会重新获取数据并调用回调函数更新视图。

Vue 3 数据绑定实现

整体思路

Vue 3 使用 ES6 的 Proxy 对象来实现数据劫持,结合 WeakMap 进行依赖收集,相比 Vue 2 更加灵活高效。核心部分包括 reactive 函数创建响应式对象、effect 函数用于副作用收集、tracktrigger 函数进行依赖收集和触发更新。

详细实现
1. reactive 函数:创建响应式对象
// reactive 函数用于创建响应式对象
function reactive(target) {const handler = {get(target, key, receiver) {// 收集依赖track(target, key);return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {const oldValue = target[key];const result = Reflect.set(target, key, value, receiver);if (oldValue!== value) {// 触发更新trigger(target, key);}return result;}};return new Proxy(target, handler);
}

reactive 函数使用 Proxy 对象拦截对象的 getset 操作。在 get 操作中调用 track 进行依赖收集,在 set 操作中调用 trigger 触发更新。

2. 依赖收集和触发更新
// 用于存储对象及其依赖映射的 WeakMap
const targetMap = new WeakMap();function track(target, key) {let depsMap = targetMap.get(target);if (!depsMap) {targetMap.set(target, (depsMap = new Map()));}let dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = new Set()));}if (activeEffect) {dep.add(activeEffect);}
}function trigger(target, key) {const depsMap = targetMap.get(target);if (!depsMap) {return;}const dep = depsMap.get(key);if (dep) {dep.forEach(effect => effect());}
}

targetMap 是一个 WeakMap,用于存储对象及其依赖映射。track 函数负责收集依赖,将 activeEffect 添加到对应的依赖集合中。trigger 函数负责触发更新,遍历依赖集合并执行其中的副作用函数。

3. effect 函数:副作用收集
let activeEffect = null;function effect(fn) {const _effect = function () {activeEffect = _effect;fn();activeEffect = null;};_effect();return _effect;
}

effect 函数用于创建副作用函数,在执行副作用函数之前将其赋值给 activeEffect,以便在 track 函数中进行依赖收集。

总结

  • Vue 2:使用 Object.defineProperty() 实现数据劫持,需要递归处理对象属性,对于新增和删除属性需要额外处理。
  • Vue 3:使用 Proxy 对象实现数据劫持,能拦截更多操作,无需递归处理,对新增和删除属性的处理更加自然,性能和灵活性更高。

相关文章:

【结合vue源码,分析vue2及vue3的数据绑定实现原理】

结合vue源码&#xff0c;分析vue2及vue3的数据绑定实现原理 Vue 2 数据绑定实现整体思路详细实现1. Observer 类&#xff1a;数据劫持2. Dep 类&#xff1a;依赖收集3. Watcher 类&#xff1a;订阅者 Vue 3 数据绑定实现整体思路详细实现1. reactive 函数&#xff1a;创建响应式…...

WebGPU:前端图形技术的革命性进化与WebGL的未来

WebGPU&#xff1a;前端图形技术的革命性进化与WebGL的未来 WebGPU作为新一代Web图形API&#xff0c;正在引发前端图形领域的深刻变革。本文将全面剖析WebGPU的技术优势、性能表现、应用场景&#xff0c;以及它与WebGL的关系和未来发展趋势。 WebGPU与WebGL的技术代差 WebGP…...

如何实现H5端对接钉钉登录并优雅扩展其他平台

如何实现H5端对接钉钉登录并优雅扩展其他平台 钉钉H5登录逻辑后端代码如何实现&#xff1f;本次采用策略模式工厂方式进行定义接口确定会使用的基本鉴权步骤具体逻辑类进行实现采用注册表模式&#xff08;Registry Pattern&#xff09;抽象工厂进行基本逻辑定义具体工厂进行对接…...

Android MediaStore访问的外部存储公共空间都不需要申请权限,这些目录具体指的是哪些

在 Android 10 及更高版本中&#xff0c;通过 MediaStore 访问以下 ​​外部存储公共目录​​ 时&#xff0c;如果操作的是应用自己创建的文件&#xff0c;则​​无需申请存储权限​​。这些目录属于系统明确定义的媒体集合&#xff0c;具体包括&#xff1a; 1. 媒体类型目录​…...

Java中的Exception和Error有什么区别?还有更多扩展

概念 在Java中&#xff0c;Exception和Error都是Throwable的子类&#xff0c;用于处理程序中的错误和异常情况。 然而&#xff0c;它们在用途和处理方式上有显著的不同&#xff1a; Exception&#xff1a; 用于表示程序在正常运行过程中可能出现的错误&#xff0c;如文件未找…...

LabVIEW真空度监测与控制系统

开发了一种基于LabVIEW的真空度信号采集与管理系统&#xff0c;该系统通过图形化编程语言实现了真空度的高精度测量和控制。利用LabVIEW的强大功能&#xff0c;研制了相应的硬件并设计了完整的软件解决方案&#xff0c;以满足工业应用中对真空度监测的精确要求。 项目背景 随着…...

虚拟dom工作原理以及渲染过程

浏览器渲染引擎工作流程都差不多&#xff0c;大致分为5步&#xff0c;创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting 第一步&#xff0c;用HTML分析器&#xff0c;分析HTML元素&#xff0c;构建一颗DOM树(标记化和树构建)。 第二步&#xff0c;用…...

数据采集爬虫三要素:User-Agent、随机延迟、代理ip

做爬虫的朋友都懂&#xff1a;你刚打开一个页面&#xff0c;还没来得及发第二个请求&#xff0c;服务器已经把你当成了“可疑流量”。403、429、验证码、JS挑战……这些“欢迎仪式”你是不是也经常收到&#xff1f;防爬策略越来越猛&#xff0c;采集工程师越来越秃。 但别慌&am…...

汽车的四大工艺

文章目录 冲压工艺核心流程关键技术 焊接工艺核心流程 涂装工艺核心流程 总装工艺核心流程终检与测试静态检查动态检查四轮定位制动转鼓测试淋雨测试总结 简单总结下汽车的四大工艺&#xff08;从网上找了一张图&#xff0c;感觉挺全面的&#xff09;。 冲压工艺 将金属板材通过…...

【JVM是什么?JVM解决什么问题?JVM在JDK体系中是什么?虚拟机和JVM、操作系统是什么关系?】

1. JVM 是什么&#xff1f; JVM&#xff08;Java Virtual Machine&#xff0c;Java 虚拟机&#xff09; 是一个虚拟的计算机程序&#xff0c;它是 Java 程序运行的核心环境。JVM 的主要职责是加载、验证、解释或编译 Java 字节码&#xff08;.class 文件&#xff09;&#xff…...

21 天 Python 计划:MySQL中DML与权限管理

文章目录 前言一、介绍二、MySQL数据操作&#xff1a;DML2.1 插入数据&#xff08;INSERT&#xff09;2.1.1 插入完整数据&#xff08;顺序插入&#xff09;2.1.2 指定字段插入数据2.1.3 插入多条记录2.1.4 插入查询结果 2.2 更新数据&#xff08;UPDATE&#xff09;2.3 删除数…...

10-MySQL-性能优化思路

1、优化思路 当我们发现了一个慢SQL的问题的时候,需要做性能优化,一般我们是为了提高SQL查询更快,一个查询的流程由下图的各环节组成,每个环节都会消耗时间,要减少消耗时候需要从各个环节都分析一遍。 2 连接配置优化 第一个环节是客户端连接到服务端,这块可能会出现服务…...

MySQL学习笔记十

第十二章汇总数据 12.1聚集函数 聚集函数运行在行组上&#xff0c;计算和返回单个值。 12.1.1AVG()函数 输入&#xff1a; SELECT AVG(prod_price) AS avg_price FROM products; 输出&#xff1a; 说明&#xff1a;AVG()函数通过对表中行数计数并计算特定列值之和&#…...

在Halcon的语义分割中,过度拟合解决方法

在Halcon语义分割中出现过拟合是比较常见的问题&#xff0c;以下是一些解决方法。 数据方面 - 扩大数据集&#xff1a;收集更多不同场景、角度、光照条件下的图像数据。例如&#xff0c;在做工业零件语义分割时&#xff0c;如果仅用少量固定角度和光照下的零件图像训练&#xf…...

Active Directory 域服务

1.活动目录有什么特点 1. 目录服务 集中管理&#xff1a;提供集中式的用户、计算机、组和其他资源的管理。 结构化存储&#xff1a;以层次结构的方式存储信息&#xff0c;便于组织和检索。 2. 域和林结构 域&#xff08;Domain&#xff09;&#xff1a;一个逻辑分组&#x…...

Redis快的原因

1、基于内存实现 Redis将所有数据存储在内存中&#xff0c;因此它可以非常快速地读取和写入数据&#xff0c;而无需像传统数据库那样将数据从磁盘读取和写入磁盘&#xff0c;这样也就不受I/O限制。 2、I/O多路复用 多路指的是多个socket连接&#xff1b;复用指的是复用一个线…...

Android 自己的智能指针

在 Android 系统中&#xff0c;强指针模板类&#xff08;sp<T>&#xff09; 是一种基于引用计数的智能指针实现&#xff0c;专门用于管理对象的生命周期。它被广泛用于 Android Framework 的底层&#xff08;Native 层/C 代码&#xff09;&#xff0c;尤其是与 Binder 通…...

如何在React中集成 PDF.js?构建支持打印下载的PDF阅读器详解

本文深入解析基于 React 和 PDF.js 构建 PDF 查看器的实现方案&#xff0c;该组件支持 PDF 渲染、图片打印和下载功能&#xff0c;并包含完整的加载状态与错误处理机制。 完整代码在最后 一个PDF 文件&#xff1a; https://mozilla.github.io/pdf.js/web/compressed.tracemo…...

【完美解决】VSCode连接HPC节点,已配置密钥却还是提示需要输入密码

目录 问题描述软件版本原因分析错误逻辑链 解决方案总结 问题描述 本人在使用 ​​VSCode Remote-SSH 插件​​连接超算集群节点时&#xff0c;遇到以下问题&#xff1a;已正确配置 SSH 密钥&#xff0c;且 VSCode 能识别密钥文件&#xff08;如图1&#xff09;&#xff0c;但在…...

智能DNS解析:解决高防IP地区访问异常的实战指南

摘要&#xff1a;针对高防IP在部分地区无法访问的问题&#xff0c;本文设计基于智能DNS的流量调度方案&#xff0c;提供GeoDNS配置与故障切换代码示例。 一、问题背景 运营商误拦截或线路波动可能导致高防IP在福建、江苏等地访问异常。传统切换方案成本高&#xff0c;智能DNS可…...

【JSON2WEB】16 login.html 登录密码加密传输

【JSON2WEB】系列目录 【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSO…...

ruby超高级语法

以下是 Ruby 中一些 极度硬核 的语法和底层特性&#xff0c;涉及元编程的深渊、虚拟机原理、语法黑魔法等&#xff0c;适用于追求极限的 Ruby 开发者&#xff1a; 高级语法一 一、语法核弹级操作 1. 动态修改继承链 class A; def foo; "A"; end end class B; def …...

第十二天 - Flask/Django基础 - REST API开发 - 练习:运维管理后台API

从零开始用Flask/Django构建运维管理后台API&#xff08;实战指南&#xff09; 前言&#xff1a;为什么选择Python Web框架&#xff1f; 在运维自动化领域&#xff0c;构建管理后台是每个运维工程师的必修课。本文将通过Flask和Django两个主流框架&#xff0c;手把手教你构建…...

Docker 容器内运行程序的性能开销

在 Docker 容器内运行程序通常会有一定的性能开销&#xff0c;但具体损失多少取决于多个因素。以下是详细分析&#xff1a; 1. CPU 性能 理论开销&#xff1a;容器直接共享宿主机的内核&#xff0c;CPU 调度由宿主机管理&#xff0c;因此 CPU 运算性能几乎与原生环境一致&…...

从递归入手一维动态规划

从递归入手一维动态规划 1. 509. 斐波那契数 1.1 思路 递归 F(i) F(i-1) F(i-2) 每个点都往下展开两个分支&#xff0c;时间复杂度为 O(2n) 。 在上图中我们可以看到 F(6) F(5) F(4)。 计算 F(6) 的时候已经展开计算过 F(5)了。而在计算 F(7)的时候&#xff0c;还需要…...

【2025年认证杯数学中国数学建模网络挑战赛】A题解题思路与模型代码

【2025年认证杯数学建模挑战赛】A题 该题为典型的空间几何建模轨道动力学建模预测问题。 ⚙ 问题一&#xff1a;利用多个天文台的同步观测&#xff0c;确定小行星与地球的相对距离 问题分析 已知若干地面天文台的观测数据&#xff1a;方位角 (Azimuth) 和 高度角 (Altitude)&…...

蓝桥杯备赛 Day16 单调数据结构

单调栈和单调队列能够动态的维护&#xff0c;还需用1-2两个数组在循环时从单调栈和单调队列中记录答案 单调栈 要点 1.时刻保持内部元素具有单调性质的栈(先进后出),核心是:入栈时逐个删除所有"更差的点",一般可分为单调递减栈、单调递增栈、单调不减栈、单调不增…...

轻量级爬虫框架Feapder入门:快速搭建企业级数据管道

一、目标与前置知识 1. 目标概述 本教程的主要目标是&#xff1a; 介绍轻量级爬虫框架 Feapder 的基本使用方式。快速搭建一个采集豆瓣电影数据的爬虫&#xff0c;通过电影名称查找对应的电影详情页并提取相关信息&#xff08;电影名称、导演、演员、剧情简介、评分&#xf…...

golang gmp模型分析

思维导图&#xff1a; 1. 发展过程 思维导图&#xff1a; 在单机时代是没有多线程、多进程、协程这些概念的。早期的操作系统都是顺序执行 单进程的缺点有&#xff1a; 单一执行流程、计算机只能一个任务一个任务进行处理进程阻塞所带来的CPU时间的浪费 处于对CPU资源的利用&…...

深入理解Java Optional:告别NullPointerException的优雅方式

大家好&#xff01;今天我们来聊聊Java 8引入的一个超实用类 - Optional。不是那个让你重启电脑的CtrlAltDel哦&#xff01;&#x1f604; 这是一个能让我们优雅处理null值的工具类&#xff0c;彻底告别烦人的NullPointerException&#xff01; 一、为什么需要Optional&#x…...