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

news/2025/3/31 11:00:40/

实现 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.ppmy.cn/news/1276004.html

相关文章

探秘 AJAX:让网页变得更智能的异步技术(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

做一个类似东郊到家系统需要哪些功能?

随着移动互联网的普及&#xff0c;越来越多的人开始通过手机预约按摩服务。按摩预约小程序&#xff0c;作为一种方便快捷的预约方式&#xff0c;让用户可以随时随地预约按摩服务。那么&#xff0c;按摩预约小程序的开发周期是多长呢&#xff1f;它又有哪些功能呢&#xff1f;本…

【十】python状态设计模式

一、引言 在软件设计中&#xff0c;设计模式是解决常见问题的最佳实践。它们提供了一种重用设计的方法&#xff0c;使得代码更易于理解、维护和扩展。状态设计模式是行为设计模式的一种&#xff0c;它允许对象在其内部状态改变时改变其行为。当控制一个对象的状态转换条件表达…

pytest--allure报告中添加用例详情

前言 前面介绍了如何生成allure的报告&#xff0c;看着allure的页面非常好看&#xff0c;但是感觉少了一些内容&#xff0c;allure还可以增加一些用例详情内容&#xff0c;这样让我们的报告看着更加绚丽。 allure增加用例详情 我们可以在报告测试套件中增加用例详情内容。 …

Android开发——组合函数、注解与连接Android设备

1、JetPack Compose、组合函数与注解和文本修改 1、JetPack Compose&#xff1a;Jetpack Compose 是由 Google 推出的用于构建 Android 用户界面的现代化工具包。它是一个声明式的 UI 工具包&#xff0c;用于简化 Android 应用程序的用户界面设计和开发。Jetpack Compose 采用…

飞天使-k8s知识点2-安装

文章目录 安装包参考地址安装前准备准备机器 keepalived 服务器安装haproxyharbor 略docker 19.03-15 版本安装略 所有节点yum安装kubelet kubeadm kubectl准备镜像 测试环境可以单节点安装单节点部署网络组件生产环境多master 安装,基于本文开头安装的keepalived,通过haproxy …

el-table 实现行拖拽排序

element ui 表格实现拖拽排序的功能&#xff0c;可以借助第三方插件Sortablejs来实现。 引入sortablejs npm install sortablejs --save组件中使用 import Sortable from sortablejs;<el-table ref"el-table":data"listData" row-key"id" …