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

Vue2 响应式系统设计原理与实现

文章目录

  • Vue2 响应式系统设计原理与实现

Vue2 响应式系统设计原理与实现

Vue2 的响应式原理主要基于以下几点:

使用 Object.defineProperty () 方法对数据对象的属性进行劫持
当数据发生变化时,通知依赖该数据的视图进行更新
实现一个发布 - 订阅模式,包含 Watcher(订阅者)、Dep(依赖收集器)等核心概念

创建以下几个核心部分:

Observer:递归地将数据对象的所有属性转换为响应式
Dep:依赖收集器,负责收集和通知订阅者
Watcher:订阅者,当数据变化时执行相应的回调函数
下面我们来简单实现以下

// 依赖收集器类:管理某个数据的所有依赖(订阅者)
class Dep {// 构造函数初始化constructor() {// 存储所有订阅者的数组this.subscribers = [];}// 添加订阅者到收集器addSub(sub) {// 检查订阅者是否存在且有update方法if (sub && sub.update) {this.subscribers.push(sub);}}// 通知所有订阅者数据已更新notify() {// 遍历所有订阅者并调用其update方法this.subscribers.forEach(sub => {sub.update();});}
}// 订阅者类:代表一个依赖,数据变化时执行更新操作
class Watcher {// 构造函数:接收Vue实例、属性名和回调函数constructor(vm, key, callback) {this.vm = vm;         // 存储Vue实例的引用this.key = key;       // 要监视的数据属性名this.callback = callback; // 数据变化时要执行的回调函数this.value = this.get();  // 初始化时获取值,触发getter完成依赖收集}// 获取数据并将当前订阅者添加到依赖收集器get() {// 将当前订阅者设为Dep的目标,标记为当前需要收集的依赖Dep.target = this;// 访问数据属性,触发其getter,从而完成依赖收集const value = this.vm[this.key];// 重置Dep.target,避免后续操作错误收集依赖Dep.target = null;// 返回获取到的值return value;}// 数据变化时执行的更新方法update() {// 获取新值const newValue = this.get();// 保存旧值const oldValue = this.value;// 只有当新旧值不同时才执行回调if (newValue !== oldValue) {// 更新当前值为新值this.value = newValue;// 调用回调函数,并将Vue实例作为上下文,传入新值和旧值this.callback.call(this.vm, newValue, oldValue);}}
}// 将普通对象转换为响应式对象的函数
function observe(data) {// 如果数据不是对象或为null,则无需处理if (!data || typeof data !== 'object') {return;}// 创建观察者实例处理数据return new Observer(data);
}// 观察者类:负责将对象的所有属性转换为响应式
class Observer {// 构造函数:接收需要处理的数据对象constructor(data) {this.data = data;// 遍历对象属性并处理this.walk(data);}// 遍历对象的所有属性walk(data) {// 获取对象所有自有属性的键名Object.keys(data).forEach(key => {// 为每个属性定义响应式this.defineReactive(data, key, data[key]);});}// 核心方法:使用Object.defineProperty定义响应式属性defineReactive(obj, key, val) {// 为当前属性创建一个依赖收集器const dep = new Dep();// 如果属性值是对象,递归处理使其也成为响应式observe(val);// 使用Object.defineProperty劫持属性的getter和setterObject.defineProperty(obj, key, {enumerable: true,   // 允许属性被枚举(例如在for...in循环中)configurable: true, // 允许属性被配置(例如删除属性)// 当属性被访问时触发的getterget() {// 如果当前有需要收集的依赖(Dep.target存在)if (Dep.target) {// 将当前依赖添加到收集器中dep.addSub(Dep.target);}// 返回属性值return val;},// 当属性被修改时触发的setterset(newVal) {// 如果新值和旧值相同,则不做处理if (newVal === val) {return;}// 更新属性值val = newVal;// 如果新值是对象,需要将其转换为响应式observe(newVal);// 通知所有依赖当前属性的订阅者数据已更新dep.notify();}});}
}// 简化版Vue类:整合响应式系统
class Vue {// 构造函数:接收配置选项constructor(options) {this.$options = options;  // 存储配置选项this.$data = options.data; // 存储数据对象// 将数据转换为响应式observe(this.$data);// 将data中的属性代理到Vue实例上,方便直接访问this.proxyData(this.$data);// 如果有created生命周期钩子,执行它if (options.created) {options.created.call(this);}}// 数据代理方法:使vm.xxx等价于vm.$data.xxxproxyData(data) {// 遍历data的所有属性Object.keys(data).forEach(key => {// 在Vue实例上定义与data属性同名的属性Object.defineProperty(this, key, {// 当访问vm.xxx时,返回vm.$data.xxx的值get() {return data[key];},// 当修改vm.xxx时,同步修改vm.$data.xxxset(newVal) {data[key] = newVal;}});});}// 提供$watch方法,用于监视数据变化$watch(key, callback) {// 创建一个新的订阅者,关联到指定的属性和回调new Watcher(this, key, callback);}
}// 使用示例
const vm = new Vue({data: {message: 'Hello Vue',count: 0,user: {name: 'John'}},created() {console.log('初始化完成:', this.message);}
});// 添加监听器,当message变化时触发
vm.$watch('message', (newVal, oldVal) => {console.log(`message变化: ${oldVal} -> ${newVal}`);
});// 添加监听器,当count变化时触发
vm.$watch('count', (newVal, oldVal) => {console.log(`count变化: ${oldVal} -> ${newVal}`);
});// 添加监听器,当user.name变化时触发
vm.$watch('user.name', (newVal, oldVal) => {console.log(`user.name变化: ${oldVal} -> ${newVal}`);
});// 测试数据变化,观察是否触发更新
vm.message = 'Hello World'; // 触发message的更新
vm.count = 1; // 触发count的更新
vm.user.name = 'Jane'; // 触发user.name的更新
http://www.xdnf.cn/news/1345195.html

相关文章:

  • 【Java并发编程】Java多线程深度解析:状态、通信与停止线程的全面指南
  • 多态(polymorphism)
  • celery
  • 学习python第12天
  • 基于Python的伊人酒店管理系统 Python+Django+Vue.js
  • 探索Thompson Shell:Unix初代Shell的智慧
  • Linux之Ubuntu入门:Vmware中虚拟机中的Ubuntu中的shell命令-常用命令
  • 解决 PyTorch 导入错误:undefined symbol: iJIT_NotifyEvent
  • MTK Linux DRM分析(十一)- MTK KMS Panel显示屏驱动
  • 使用html+css+javascript练习项目布局--创建导航栏
  • Linux驱动开发笔记(六)——pinctrl GPIO
  • MTK Linux DRM分析(十三)- Mediatek KMS实现mtk_drm_drv.c(Part.1)
  • chapter07_初始化和销毁方法
  • 【连接器专题】连接器接触界面的理解
  • CoreShop微信小程序商城框架开启多租户-添加一个WPF客户端以便进行本地操作--读取店铺信息(6)
  • 彩笔运维勇闯机器学习--最小二乘法的数学推导
  • 在线教育领域的视频弹题功能如何打造高互动性在线课程
  • 【Tech Arch】Hadoop YARN 大数据集群的 “资源管家”
  • 全栈开发:从LAMP到云原生的技术革命
  • Kali Linux 发布重构版Vagrant镜像:通过命令行快速部署预配置DebOS虚拟机
  • Pandas中的SettingWithCopyWarning警告出现原因及解决方法
  • DbLens:告别手动Mock数据,右键一键智能生成数据库内容
  • httpclient与hertzclient在处理Host header时的差别
  • 【GPT入门】第53课 LlamaFactory微调效果与vllm部署效果不一致问题解决
  • open webui源码分析6-Function
  • FPGA学习笔记——简单的IIC读写EEPROM
  • FPGA高端项目:图像采集+Aurora 8B10B+UDP图传架构,基于GTH高速收发器的光口转网口,提供工程源码和技术支持
  • IntelliJ IDEA 常用快捷键笔记(Windows)
  • SRE系列(二) | 从可用性到 SLI/SLO
  • 【数据结构】B 树——高度近似可”独木成林“的榕树——详细解说与其 C 代码实现