Vue2是如何利用Object.defineProperty实现数据的双向绑定?

我们之前说道过Object.defineProperty方法有一关键特性,就是数据劫持,通过get/set 拦截属性的读取和修改操作。Vue主要是通过数据劫持结合发布-订阅模式来实现的,利用Object.defineProperty来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

1. Vue2 实现过程

我们来具体看一下Vue的实现过程吧

  1. 在Vue类中,接收一个options参数,里面包含data等选项,在构造函数里,我需要将data对象进行响应式处理。这一步应该是通过遍历data的所有属性,并用Object.defineProperty来重新定义它们,使得当属性被访问时收集依赖,被修改时触发更新。

  2. 对于每个属性,需要有一个Dep(依赖收集器)来管理所有的Watcher实例。每当属性被读取时,就将当前的Watcher添加到Dep中;当属性被修改时,Dep就会通知所有Watcher执行更新操作。

  3. 既然有Watcher实例,那么就有一个Watcher类,它负责订阅属性的变化,并在变化时执行回调函数,比如更新视图。在Watcher的构造函数中,需要将自己设置为全局的一个目标实例,然后触发属性的getter,从而将Watcher添加到Dep中。

  4. 还有,可能需要一个编译器(Compiler)来解析模板中的指令,比如v-model,然后初始化相应的数据绑定和事件监听。例如,对于v-model,需要将input元素的值与数据属性同步,并在输入时更新数据属性,从而触发数据的setter,进一步通知更新。

现在,我需要一步步构建这些类。首先是Vue类,它初始化数据,进行响应式处理。然后,每个数据属性都有一个对应的Dep实例。当数据被访问时,收集当前的Watcher;数据变化时,通知Dep里的所有Watcher更新。

然后,Watcher类的作用是连接数据属性和具体的更新函数,比如更新DOM。当数据变化时,Watcher会被触发,执行更新函数。

在这里插入图片描述

2. Vue2 组成结构

总体来说应有以下结构:

  1. Vue类:

作用:

  • 接收options,处理data。

  • 将data的属性转为getter/setter。

  • 调用observe函数进行响应式处理。

  1. Observer类:

作用:

  • 遍历对象的所有属性,转为响应式。

  • 每个属性有一个Dep实例。

  1. Dep类:

作用:

  • 收集Watcher实例(subs数组)。

  • 提供addSub方法添加Watcher。

  • 提供notify方法触发所有Watcher的update。

  1. Watcher类:

作用:

  • 在实例化时,将自身赋值给Dep.target。

  • 访问对应的数据属性,触发getter,从而将自身添加到Dep中。

  • 定义update方法,执行回调函数(比如更新DOM)。

接下来是具体的代码实现。可能还需要处理多个Watcher的情况,以及如何将它们正确地关联到对应的Dep。

然后,需要处理模板编译。比如,对于v-model,需要找到元素,添加事件监听,当输入时更新数据属性。同时,初始化input的值。对于{{}}文本,需要替换为对应的数据,并建立Watcher,当数据变化时更新文本内容。

不过,我们现在只是简单展示一下实现原理,直接操作DOM元素,而不是完整的编译器。例如,手动绑定一个input和显示元素,展示双向绑定的效果。

现在,我需要将上述思路转化为代码。可能先从Vue类开始:

class Vue{constructor(options){this.$options = options;this._data = typeof options.data === 'function' ? options.data() : options.data;//调用Observer函数进行响应式处理new Observer(this._data);this.compile();}compile() {// 处理v-model绑定const inputs = document.querySelectorAll('[v-model]');inputs.forEach(input => {const key = input.getAttribute('v-model').trim();input.value = this._data[key];input.addEventListener('input', (e) => {this._data[key] = e.target.value;});});// 处理{{ }} 绑定const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配elements.forEach(element => {const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);if (matches) {const key = matches[1].trim();new Watcher(this._data, key, (newVal) => {element.innerHTML = newVal;});// 初始化显示element.innerHTML = this._data[key];}});}
}
 // Observer类:// 遍历对象的所有属性,转为响应式// 每个属性有一个Dep实例     class Observer{constructor(data){this.walk(data);}walk(data){Object.keys(data).forEach(key =>{this.defineReactive(data,key,data[key]);})console.log(Object.keys(data))}defineReactive(obj,key,val){const dep = new Dep();Object.defineProperty(obj,key,{enumberable:true,configurable:true,get(){if(Dep.target){dep.addSub(Dep.target)}return val;},set(newVal){if(newVal === val) return;val = newVal;dep.notify();}})}}
// 收集Watcher实例(subs数组)
// 提供addSub方法添加wathcer// 提供notify方法触发所有的Watcher 和 update。class Dep{constructor(){this.subs = [];}addSub(sub){this.subs.push(sub);}notify(){this.subs.forEach(sub => sub.update())}}Dep.target = null;
// 在实例化时,将自身赋值给Dep.target
// 访问对应的数据属性,触发getter,从而将自身添加到Dep中。// 定义update方法,执行回调函数(比如更新Dom)class Watcher{constructor(data,key,cb){this.data = data;this.key = key;this.cb= cb;this.value = this.get();}get(){Dep.target = this; // 将当前Watcher实例赋值给Dep.targetconst value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中Dep.target = null;//收集完清空return value;}update(){const newVal = this.data[this.key];if (newVal !== this.value) {this.value = newVal;this.cb(newVal);}}}

以下是完成的可运行代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>vue2数据的双向绑定</title>
</head>
<body><input type="text" v-model="message"><div id="app"><p>{{ message }}</p></div><script>// 收集Watcher实例(subs数组)// 提供addSub方法添加wathcer// 提供notify方法触发所有的Watcher 和 update。class Dep{constructor(){this.subs = [];}addSub(sub){this.subs.push(sub);}notify(){this.subs.forEach(sub => sub.update())}}Dep.target = null;// 在实例化时,将自身赋值给Dep.target// 访问对应的数据属性,触发getter,从而将自身添加到Dep中。// 定义update方法,执行回调函数(比如更新Dom)class Watcher{constructor(data,key,cb){this.data = data;this.key = key;this.cb= cb;this.value = this.get();}get(){Dep.target = this; // 将当前Watcher实例赋值给Dep.targetconst value = this.data[this.key]; // 触发getter,从而将Watcher添加到Dep中Dep.target = null;//收集完清空return value;}update(){const newVal = this.data[this.key];if (newVal !== this.value) {this.value = newVal;this.cb(newVal);}}}class Vue{constructor(options){this.$options = options;this._data = typeof options.data === 'function' ? options.data() : options.data;//调用Observer函数进行响应式处理new Observer(this._data);this.compile();}compile() {// 处理v-model绑定const inputs = document.querySelectorAll('[v-model]');inputs.forEach(input => {const key = input.getAttribute('v-model').trim();input.value = this._data[key];input.addEventListener('input', (e) => {this._data[key] = e.target.value;});});// 处理{{ }} 绑定const elements = document.querySelectorAll('p,span,div'); // 简化处理,实际应根据文本内容匹配elements.forEach(element => {const matches = element.innerHTML.match(/\{\{\s*(.*?)\s*\}\}/);if (matches) {const key = matches[1].trim();new Watcher(this._data, key, (newVal) => {element.innerHTML = newVal;});// 初始化显示element.innerHTML = this._data[key];}});}}// Observer类:// 遍历对象的所有属性,转为响应式// 每个属性有一个Dep实例     class Observer{constructor(data){this.walk(data);}walk(data){Object.keys(data).forEach(key =>{this.defineReactive(data,key,data[key]);})console.log(Object.keys(data))}defineReactive(obj,key,val){const dep = new Dep();Object.defineProperty(obj,key,{enumberable:true,configurable:true,get(){if(Dep.target){dep.addSub(Dep.target)}return val;},set(newVal){if(newVal === val) return;val = newVal;dep.notify();}})}}new Vue({data: () => ({message: "hello world"})});</script>
</body>
</html>

看完了此篇文章,您是不是对Vue2实现原理有了更深刻的理解呢,如果觉得对您有帮助,还请一键三连哦!非常感谢!

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

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

相关文章

【Linux网络】认识协议(TCP/UDP)、Mac/IP地址和端口号、网络字节序、socket套接字

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;Linux 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 1、初识协议2、UDP、TCP3、Mac、IP地址4、端口号5、网络字节序6、socket 1、初识协议 协议就是一种约定。如何让不同厂商生产的计…

基于flask+vue框架的的医院预约挂号系统i1616(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能:用户,医生,科室信息,就诊信息,医院概况,挂号信息,诊断信息,取消挂号 开题报告内容 基于FlaskVue框架的医院预约挂号系统开题报告 一、研究背景与意义 随着医疗技术的不断进步和人们健康意识的日益增强&#xff0c;医院就诊量逐年增加。传统的现场…

影刀RPA中级证书-Excel进阶-开票清单

1.操作题需求 请参照视频内容&#xff0c;将开票账单表格中的数据整理成开票清单。请下载 开票账单.xlsx 整理规则如下&#xff1a; 1. 金额为0的数据为赠品&#xff0c;无需开票&#xff0c;需删除2. 开票清单需要从开票账单中获取的数据包括有开票名称、数量、金额、税率&…

浅谈模组-相机鬼像

一&#xff0e;前言 在成像中&#xff0c;我们常常会遇到肉眼观测的真实世界中&#xff0c;不存在的异常光影出现在画面中&#xff0c;并伴有各种颜色&#xff0c;我们将这个物体称为鬼像。某些鬼像可能会对图像产生美感的体验&#xff0c;但是大多数的鬼像都会对图像的质量以…

Python爬虫实战:爬取豆瓣电影

目录 引言 1. 爬虫基础 1.1 什么是爬虫&#xff1f; 1.2 Python爬虫常用库 2. 实战&#xff1a;抓取豆瓣电影Top250 2.1 安装依赖库 2.2 发送HTTP请求 ​编辑 2.3 解析HTML ​编辑 2.4 存储数据 2.5 完整代码 3. 进阶&#xff1a;处理分页和动态内容 3.1 抓取多页…

Redis7——基础篇(六)

前言&#xff1a;此篇文章系本人学习过程中记录下来的笔记&#xff0c;里面难免会有不少欠缺的地方&#xff0c;诚心期待大家多多给予指教。 基础篇&#xff1a; Redis&#xff08;一&#xff09;Redis&#xff08;二&#xff09;Redis&#xff08;三&#xff09;Redis&#x…

Windows 下 Ollama 安装deepseek本地模型

Windows 下 Ollama 安装deepseek本地模型 安装 Ollama 下载 Ollama 下载链接&#xff1a;https://ollama.org.cn/download/windows 下载完成后&#xff0c;按照提示进行安装。 安装过程 安装完成后&#xff0c;安装页面会自动关闭&#xff0c;这是正常现象。 接下来&#…

【鸿蒙开发】第四十三章 Notification Kit(用户通知服务)

目录​​​​​​​ 1 简介 1.1 使用场景 1.2 能力范围 1.3 业务流程 1.4 通知样式 1.5 约束限制 1.6 与相关Kit的关系 2 请求通知授权 2.1 接口说明 2.2 开发步骤 3 管理通知角标 3.1 接口说明 3.2 开发步骤 4 管理通知渠道 4.1 通知渠道类型说明 4.2 接口说明…

bboss v7.3.5来袭!新增异地灾备机制和Kerberos认证机制,助力企业数据安全

ETL & 流批一体化框架 bboss v7.3.5 发布&#xff0c;多源输出插件增加为特定输出插件设置记录过滤功能&#xff1b;Elasticsearch 客户端新增异地双中心灾备机制&#xff0c;提升框架高可用性&#xff1b;Elasticsearch client 和 http 微服务框架增加对 Kerberos 认证支持…

利用非球面透镜进行激光光束重组

这是高斯分布激光束重塑透镜设计的分步演示。该透镜用于将高斯分布转换为成像平面上的均匀照明。 输入激光光束轮廓&#xff1a; 光学结构&#xff1a; Excel 中坐标的数学计算&#xff1a; 输出照度曲线&#xff1a;

(一)趣学设计模式 之 单例模式!

目录 一、啥是单例模式&#xff1f;二、为什么要用单例模式&#xff1f;三、单例模式怎么实现&#xff1f;1. 饿汉式&#xff1a;先下手为强&#xff01; &#x1f608;2. 懒汉式&#xff1a;用的时候再创建&#xff01; &#x1f634;3. 枚举&#xff1a;最简单最安全的单例&a…

nacos编写瀚高数据库插件

1、下载nacos源码 git clone gitgithub.com:alibaba/nacos.git 2、引入瀚高驱动 <dependency><groupId>com.highgo</groupId><artifactId>jdbc</artifactId><version>${highgo.version}</version></dependency> 3、DataSource…

讯飞唤醒+VOSK语音识别+DEEPSEEK大模型+讯飞离线合成实现纯离线大模型智能语音问答。

在信息爆炸的时代&#xff0c;智能语音问答系统正以前所未有的速度融入我们的日常生活。然而&#xff0c;随着数据泄露事件的频发&#xff0c;用户对于隐私保护的需求日益增强。想象一下&#xff0c;一个无需联网、即可响应你所有问题的智能助手——这就是纯离线大模型智能语音…

后端Java Stream数据流的使用=>代替for循环

API讲解 对比 示例代码对比 for循环遍历 package cn.ryanfan.platformback.service.impl;import cn.ryanfan.platformback.entity.Algorithm; import cn.ryanfan.platformback.entity.AlgorithmCategory; import cn.ryanfan.platformback.entity.DTO.AlgorithmInfoDTO; im…

UE 播放视频

一.UI播放视频 1.导入视频文件至工程文件夹 2.文件夹内右健选择Media -> File Meida Source创建testFileMeidaSource文件。 编辑FilePath为当前视频 3.右键->Media->Media Player 创建testMediaPlayer文件 4.右键创建testMediaTexture。编辑MediaPlayer设置testMedia…

推荐一款AI大模型托管平台-OpenWebUI

推荐一款AI大模型托管平台-OpenWebUI 1. OpenWebUI 1. OpenWebUI什么? 官网地址&#xff1a;https://openwebui.com/ GitHub地址&#xff1a; https://github.com/open-webui/open-webui Open WebUI 是一个可扩展、功能丰富且用户友好的自托管 AI 平台&#xff0c;旨在完全离…

【Python项目】基于知识图谱的百科问答系统

【Python项目】基于知识图谱的百科问答系统 技术简介&#xff1a; 采用Python技术、MySQL数据库、Django框架、Scrapy爬虫等技术实现。 系统简介&#xff1a; 百科问答系统是一个基于知识图谱的问答平台&#xff0c;旨在为用户提供快速、准确的百科知识查询服务。系统通过爬…

stm32rtc实时时钟详解文章

目录 stm32 后备区域基础知识详解 stm32 bkp基础知识详解 Unix时间戳基础知识详解 stm32 rtc实时时钟基础知识详解 相关代码初始化配置 欢迎指正&#xff0c;希望对你&#xff0c;有所帮助&#xff01;&#xff01;&#xff01; stm32 后备区域基础知识详解 stm32芯片的 …

Spring Boot项目@Cacheable注解的使用

Cacheable 是 Spring 框架中用于缓存的注解之一&#xff0c;它可以帮助你轻松地将方法的结果缓存起来&#xff0c;从而提高应用的性能。下面详细介绍如何使用 Cacheable 注解以及相关的配置和注意事项。 1. 基本用法 1.1 添加依赖 首先&#xff0c;确保你的项目中包含了 Spr…

windows上vscode cmake工程搭建

安装vscode插件&#xff1a; 1.按装fastc&#xff08;主要是安装MinGW\mingw64比较方便&#xff09; 2.安装C&#xff0c;cmake&#xff0c;cmake tools插件 3.准备工作完成之后&#xff0c;按F1&#xff0c;选择cmake:Quick Start就可以创建一个cmake工程。 4.设置Cmake: G…