手动实现 Vue 2 的简易双向数据绑定(模仿源码)

实现 Vue 2 的简易双向数据绑定

Vue.js 是一个流行的前端框架,它以其简单易用的双向数据绑定而闻名。在下面的文章中,我们将探索 Vue 2 如何通过其响应式系统实现双向数据绑定,并尝试手动实现一个简化版本。

核心概念

Vue 2 的双向数据绑定基于几个关键概念:响应式系统依赖收集派发更新。它利用 JavaScript 的 Object.defineProperty 方法来跟踪数据的变化。

响应式系统的初始化

当一个 Vue 实例被创建时,它通过一个名为 observe 的函数递归地遍历所有的数据对象,并将这些对象的每个属性转换成 getter/setter。这是通过 defineReactive 函数实现的。

依赖收集和派发更新

每个组件实例都有一个相应的 Watcher 实例,它会在组件渲染过程中记录所有依赖(即数据属性)。当一个数据属性被修改时,它的 setter 会被触发,进而通知相关的 Watcher 更新视图。

实现步骤

以下是实现 Vue 2 双向绑定的简化步骤:

1. 实现 observe 函数

这个函数负责遍历并包装对象的每个属性。

function observe(obj) {// 检查obj是否是对象,如果不是对象或者为null,则不需要做响应式处理if (!obj || typeof obj !== 'object') return;// 遍历对象的每个属性,对每个属性进行响应式处理Object.keys(obj).forEach(key => {defineReactive(obj, key, obj[key]);});
}

2. 定义 defineReactive 函数

这个函数使用 Object.defineProperty 将普通属性转换为响应式属性。

function defineReactive(obj, key, val) {// 如果val本身还是对象,则需要递归处理,确保对象内的属性也是响应式的observe(val);// 创建一个依赖管理器实例,用于收集和派发当前属性的依赖let dp = new Dep();// 通过Object.defineProperty将属性转换为getter/setterObject.defineProperty(obj, key, {enumerable: true,   // 属性可枚举configurable: true, // 属性可配置get: function reactiveGetter() {// 收集依赖,当有Watcher读取该属性时,将Watcher添加到依赖列表中if (Dep.target) dp.addSub(Dep.target);return val;},set: function reactiveSetter(newVal) {// 当属性值发生变化时,更新属性的值val = newVal;// 并通知所有依赖进行更新dp.notify();}});
}

3. 创建 Dep

Dep 类是一个依赖管理器,它收集和派发依赖。

class Dep {constructor() {// 初始化依赖数组,用于存储所有依赖该属性的Watcherthis.subs = [];}// 添加一个新的依赖(Watcher)addSub(sub) {this.subs.push(sub);}// 当属性变化时,通知所有依赖执行更新操作notify() {this.subs.forEach(sub => sub.update());}
}
// 全局属性,用于暂存当前正在计算的Watcher
Dep.target = null;

4. 定义 Watcher

Watcher 类为每个组件或指令创建一个观察者实例。

class Watcher {constructor(obj, key, cb) {// 将Dep.target指向自己,用于依赖收集Dep.target = this;this.cb = cb; // 回调函数,用于更新视图this.obj = obj; // 监听的目标对象this.key = key; // 监听的对象属性this.value = obj[key]; // 触发属性的getter,进行依赖收集Dep.target = null; // 收集完依赖后,将Dep.target重置}// 当属性变化时,调用回调函数更新视图update() {this.value = this.obj[this.key];this.cb(this.value);}
}

5. 测试案例

创建一个数据对象,并观察它的变化。

var data = { name: 'yck' };
// 对数据对象data进行响应式处理
observe(data);// 更新DOM的函数
function update(value) {document.querySelector('div').innerText = value;
}// 创建一个Watcher实例,模拟对data.name的依赖收集和视图更新
new Watcher(data, 'name', update);// 修改data.name的值,触发响应式更新
data.name = 'yyy';

完整代码如下

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vue 2 Data Binding Example</title>
</head>
<body><div>{{name}}</div><script>// observe 函数:使一个对象变成响应式function observe(obj) {if (!obj || typeof obj !== 'object') return;Object.keys(obj).forEach(key => {defineReactive(obj, key, obj[key]);});}// defineReactive 函数:定义一个响应式的属性function defineReactive(obj, key, val) {observe(val); // 递归处理子属性const dep = new Dep(); // 为每个属性创建依赖管理器实例Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter() {if (Dep.target) {dep.addSub(Dep.target); // 收集依赖}return val;},set: function reactiveSetter(newVal) {if (newVal === val) return;val = newVal;dep.notify(); // 数据变化,通知所有依赖更新}});}// Dep 类:依赖管理器,管理某个属性的所有Watcherclass Dep {constructor() {this.subs = [];}addSub(sub) {this.subs.push(sub);}notify() {this.subs.forEach(sub => sub.update());}}Dep.target = null;// Watcher 类:观察者,观察属性变化并执行回调class Watcher {constructor(obj, key, cb) {Dep.target = this;this.cb = cb;this.obj = obj;this.key = key;this.value = obj[key]; // 触发getter,进行依赖收集Dep.target = null;}update() {this.value = this.obj[this.key];this.cb(this.value); // 执行回调更新视图}}// 测试代码var data = { name: 'Vue' };observe(data);// 模拟解析到 `{{name}}`,创建一个Watcher来更新视图new Watcher(data, 'name', function (value) {document.querySelector('div').innerText = value;});// 修改data.name的值,触发响应式更新setTimeout(() => {data.name = 'Vue 2';}, 2000);</script>
</body>
</html>

结论

以上代码提供了一个简单的 Vue 2 双向绑定机制的实现。尽管实际的 Vue 源码要复杂得多,但这个简化版本揭示了 Vue 数据响应系统的核心原理:通过 Object.defineProperty 实现的数据监听、依赖收集、以及基于这些依赖的视图更新。

通过理解这些基础概念,我们可以更深入地理解 Vue 的工作原理,并在需要时对其进行定制和优化。

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

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

相关文章

【每日一题】【12.20】2828.判别首字母缩略词

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 1.题目链接 2828. 判别首字母缩略词https://leetcode.cn/problems/check-if-a-string-is-an-acronym-of-words/ 2.题目描述 今天…

MySQL——表的增删查改

目录 一.Create&#xff08;创建&#xff09; 1.单行数据 全列插入 2.多行数据 指定列插入 3.插入否则更新 4. 替换 二.Retrieve&#xff08;读取&#xff09; 1. select 列 查询 2.where 条件 3.结果排序 4.筛选分页结果 三.Update &#xff08;修改&#xff09;…

单例模式详解

单例模式 1. 概述 单例模式是一种创建型设计模式&#xff0c;旨在确保一个类只有一个实例&#xff0c;并提供一个全局访问点以获取该实例。这种模式有助于控制资源的访问、管理全局配置或共享状态&#xff0c;确保系统中某个类只存在一个实例。 2. 目的 确保一个类仅有一个…

Python实现多元线性回归模型信用卡客户价值预测项目源码+数据+项目设计报告

多元线性回归——信用卡客户价值预测 一、背景 这里以信用卡客户的客户价值为例来解释客户价值预测的具体含义&#xff1a; 客户价值预测就是指预测客户在未来一段时间内能带来多少利润&#xff0c;其利润可能来自信用卡的年费、取现手续费、分期手续费、境外交易手续费等。分…

三、万语始于HelloWorld,万物基于点灯之瑞萨RX23E-A 3线RTD PT100测温

一、概述/目的 二、RX23E-APT100三线制 2.1 资料来源 2.2 接线图 2.3 工程配置AFE24ΔΣADCPGA 2.4 代码 2.5 IIR数字滤波器 三、方案对比 三、万语始于HelloWorld&#xff0c;万物基于点灯之瑞萨RX23E-A 3线RTD PT100测温 一、概述/目的 介绍工业过程控制…

DAPLink源码固件编译与制作

DAPLink源码固件编译与制作 ✨这里以Air/stm32f103cbt6固件编译为例。&#x1f4cc;DAPLink源码地址&#xff1a;https://github.com/ARMmbed/DAPLink&#x1f516; 如果不想自己生成&#xff0c;可以使用合宙提供的现成的工程以及固件&#xff1b;https://gitee.com/openLuat/…

官方指定Jmeter配置JVM堆内存方式

1.概述 在使用Jmeter做性能测试过程中&#xff0c;可能会应为默认设置的堆内存值较小出现堆内存溢出问题&#xff0c;此时解决的方式有两种&#xff0c;分布式测试和调大堆内存。下面介绍官方推荐调整堆内存方法。 2.调整Jmeter堆内存 2.1.介绍官方推荐堆内存调整方法(jmete…

stable diffusion 极简入门 核心 概念介绍 使用

一、怎么写提示词&#xff08;prompt&#xff09; 1.1 结构 一般分三部分&#xff0c;按从前到后的顺序&#xff1a;画面质量or风格、画面主体内容、其他细节/背景。 画面质量&#xff1a;如&#xff0c;masterpiece,best quality,highly detailed画面主体内容&#xff1a;如…

Android Studio 显示Cause: connect timed out

在启动其他地方复制下来的项目时&#xff0c;可能会出现Cause: connect timed out报错&#xff0c;大概率由于项目与AndroidStudio使用的Gradle 版本不一致导致。 请检查&#xff0c;包名 / gradle / wrapper / gradle-wrapper.properties 文件 &#xff0c;文件中的distribut…

mysql原理--连接的原理

1.连接简介 1.1.连接的本质 为了故事的顺利发展&#xff0c;我们先建立两个简单的表并给它们填充一点数据&#xff1a; mysql> CREATE TABLE t1 (m1 int, n1 char(1)); mysql> CREATE TABLE t2 (m2 int, n2 char(1)); mysql> INSERT INTO t1 VALUES(1, a), (2, b), (…

攻防世界-web-ics07

1. 题目描述 工控云管理系统项目管理页面解析漏洞 打开链接&#xff0c;是这样的一个界面 我们点击项目管理 可以看到&#xff0c;这里有一个查询界面&#xff0c;还有个view-source的链接&#xff0c;我们点击下view-source&#xff0c;可以看到这里面共有三段php代码 第一段…

在Spring Cloud中使用Zuul网关实现一个案例

本篇依旧是在Spring Cloud系列的博主已经搭建的Spring Cloud微服务模块上进行的&#xff0c;注意&#xff0c;本文依旧适合初学者和或者在Spring Cloud框架了解不是很深入的基础的开发者&#xff0c;本系列说不上有多高大上&#xff0c;博主坚持通过简单的案例&#xff0c;让开…

JavaFX实现简单下雨动效

下雨动效可以使用JavaFX的动画功能来实现。下面是一个简单的示例代码,实现了一个下雨的动画效果: import javafx.animation.Animation; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application…

嵌入式中GPIO的工作原理-面试工作必会技能

GPIO工作方式 1、4种输入模式 输入浮空输入上拉输入下拉模拟输入 如下图为GPIO的基本结构&#xff0c;它主要由4部分组成&#xff0c;其中我们所看到的的最右边的I/O引脚&#xff0c;也就是芯片外接可看到的引脚&#xff0c;其它的部分都是GPIO内部的结构。 ①保护二极管 保护…

【新版HI3559AV100开发注意事项(二)】

#新版HI3559AV100开发注意事项&#xff08;二&#xff09; 十一、请问海思HI3559AV100 SPC030资料里面的HI3559ADMEB_VER_C_PCB.pcb是用什么软件打开啊&#xff1f; 答&#xff1a;PADS VX 2.2 Altium designer 十二、hi3559级联问题请教 在SDK的文档中只看到了两块Hi3559板…

服务器数据恢复-EMC存储raid5磁盘物理故障离线的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 一台emc某型号存储服务器&#xff0c;存储服务器上组建了一组raid5磁盘阵列&#xff0c;阵列中有两块磁盘作为热备盘使用。存储服务器在运行过程中有两块磁盘出现故障离线&#xff0c;但是只有一块热备盘激活&#xff0c;最终导致该ra…

力扣:204. 计数质数(Python3)

题目&#xff1a; 给定整数 n &#xff0c;返回 所有小于非负整数 n 的质数的数量 。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 示例&#xff1a; 示例 1&#xff1a; 输…

【️如何理解Java中的多态】

✅如何理解Java中的多态&#xff1f; ✅理解Java中的多态 ✅ 扩展知识仓✅方法的重载✅方法的重写✅重载和重写的区别区分 ✅理解Java中的多态 多态的概念比较简单&#xff0c;就是同一操作作用于不同的对象&#xff0c;可以有不同的解释&#xff0c;产生不同的执行结果。 如果…

Java 中的内部类的定义

目录 一、成员内部类 二、静态内部类 三、局部内部类 四、匿名内部类 一、成员内部类 public class InnerClass {String name;private Integer age;static String hobby;/*** 成员内部类* 1、成员内部类中只能定义非静态属性和方法* 2、成员内部类中可以访问外部类的成员&a…

vue3表格导入导出.xlsx

在这次使用时恰好整出来了&#xff0c;希望大家也能学习到&#xff0c;特此分享出来 使用前确保安装以下模块&#xff0c;最好全局配置element-plus ### 展示一下 ### ###导出选项 ### ###导入de数据 ### 安装的模块 npm install js-table2excel // 安装js-table2excel n…