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

源码的角度分析Vue2数据双向绑定原理

什么是双向绑定

我们先从单向绑定切入,其实单向绑定非常简单,就是把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。那么双向绑定就可以从此联想到,即在单向绑定的基础上,用户更新了View,Model数据也会自动被更新,这种情况就是双向绑定。实例如下:

 当用户填写表单,View的状态就被更新了,如果此时可以自动更新Model的状态,那就相当于我们把Model和View做了双向绑定关系图如下:

双向绑定的原理是什么

我们都知道Vue是数据双向绑定的框架,双向绑定由三个重要部分组成

  • Model:应用数据以及业务逻辑
  • View:应用视图,各类UI组件
  • ViewModel:框架封装的核心,它负责将数据与视图关联起来

上面这个分层的架构方案,即是我们经常耳熟能详的MVVM,他的控制层的核心功能便是“数据双向绑定”

理解ViewModel

它的主要职责就是:

  • 数据变化后更新视图
  • 视图变化后更细数据

当然,它还有两个主要部分组成

  • 监听器:对所有的数据进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

实现双向绑定

我们还是以Vue为例,先看看Vue中双向绑定流程是什么

1.new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在Observe中(类似于Vue生命周期created之前执行的一系列初始化操作)

2.同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Complie中(类似于Vue生命周期mounted之前执行的一系列初始化操作)

3.同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数

4.由于data的某个key在一个视图中可能会出现多次,所以每个key都需要一个管家Dep来管理多个Watcher

5.将来data中数据一旦发生变化,会首先找到ui应的Dep,同时所有Watcher执行更新函数

流程图如下:

劫持监听所有属性Observe

先来一个构造函数:执行初始化,对data执行响应化处理

  class Vue {  constructor(options) {  this.$options = options;  this.$data = options.data;  // 对data选项做响应式处理  observe(this.$data);  // 代理data到vm上  proxy(this);  // 执行编译  new Compile(options.el, this);  }  
}  

对data选项进行响应化具体操作


function proxy(vm) {Object.keys(vm.$data).forEach(key=>{Object.defineProperty(vm, key, {get() {return vm.$data[key]},set(newVal) {vm.$data[key] = newVal}})})
}function observe(obj) {  if (typeof obj !== "object" || obj == null) {  return;  }  new Observer(obj);  
}  class Observer {  constructor(value) {  this.value = value;  this.walk(value);  }  walk(obj) {  Object.keys(obj).forEach((key) => {  defineReactive(obj, key, obj[key]);  });  }  
}  

编译Complie

对每个元素节点的指令进行扫面和解析,根据指令模板替换数据,同时绑定相应的更新函数

class Compile {  constructor(el, vm) {  this.$vm = vm;  this.$el = document.querySelector(el);  // 获取dom  if (this.$el) {  this.compile(this.$el);  }  }  compile(el) {  const childNodes = el.childNodes;   Array.from(childNodes).forEach((node) => { // 遍历子元素  if (this.isElement(node)) {   // 判断是否为节点  console.log("编译元素" + node.nodeName);  } else if (this.isInterpolation(node)) { // 判断是否为插值文本 {{}} console.log("编译插值⽂本" + node.textContent);  }  if (node.childNodes && node.childNodes.length > 0) {  // 判断是否有子元素  this.compile(node);  // 对子元素进行递归遍历  }  });  }  isElement(node) {  return node.nodeType == 1;  }  isInterpolation(node) {  return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent);  }  
}  

依赖收集

        Vue2.x中的响应式原理主要死依赖于Object.defineProperty()方法实现属性的getter和setter。在Vue中,每个组件实例都有一个对应的Watcher实例,Watcher实例会负责依赖的收集以及触发更新。

        具体来说,当一个组件渲染时,会执行render函数来生成Virtual DOM,并且在执行过程中,当访问到组件的data中的属性时,会触发属性的getter方法。并在getter方法中,会进行依赖收集,将当前的Watcher对象存储到当前属性的依赖列表中。

当个属性收集具体如下图:

多个属性的收集如下:

依赖收集的过程可以简单描述如下:

1.在组件渲染过程中,当访问data中的属性时,会触发属性的getter方法;

2.在getter方法中,会将当前Watcher对象存储到当前依赖列表中(Dep);

3.当属性被修改时,会触发属性的setter方法;

4.在setter方法中,会通知所有依赖于该属性的Watcher对象,执行更新操作;

这样,当数据发生变化 时,Vue能够精确的知道哪些地方需要更新,并且只更新相关的部分,提高了性能(因为只有存储了触发getter方法时的watcher,做到了对应关系)

简化版的实现代码如下:

// 定义 Dep 类,用于管理依赖
class Dep {constructor() {this.subscribers = new Set(); // 存储 Watcher 实例的集合}// 添加依赖depend() {if (activeWatcher) {this.subscribers.add(activeWatcher);}}// 通知依赖更新notify() {this.subscribers.forEach(watcher => {watcher.update();});}
}let activeWatcher = null;// 定义 Watcher 类,用于观察数据变化
class Watcher {constructor(update) {this.update = update; // 更新函数this.value = null; // 存储当前值this.get(); // 初始化时进行依赖收集}// 获取当前值,并进行依赖收集get() {activeWatcher = this;// 在这里模拟读取 data 中的属性的过程this.value = this.update();activeWatcher = null;}
}// 定义 reactive 函数,将对象转换为响应式对象
function reactive(obj) {// 遍历对象的每个属性,转换为响应式属性for (let key in obj) {let value = obj[key];const dep = new Dep(); // 每个属性对应一个依赖管理对象Object.defineProperty(obj, key, {get() {dep.depend(); // 依赖收集return value;},set(newValue) {value = newValue;dep.notify(); // 通知依赖更新}});}return obj;
}// 示例用法
const data = reactive({count: 0
});new Watcher(() => {console.log("Value updated:", data.count);
});data.count++; // 触发更新

在这个示例中

  • Dep类用于管理依赖,每个响应式属性都会对应一个'Dep'实例,用于存储依赖于该属性的'Watcher'对象
  • ’Watcher‘类用于观察数据变化,当数据发生改变时会执行更新函数
  • ’reactive‘函数用于将对象转为响应式对象,在该函数中,通过'Object.defineProperty'来定义对象的属性,实现了属性的getter和setter,从而在读取和修改属性时进行依赖收集和通知更新

 在实际的 Vue 源码中,会有更复杂的逻辑和优化,但基本原理与上述代码类似。

个人备注说明:

1.上述代码设计中为什么activeWatcher变量是全局存储,同时在Watcher类的get方法中先是指向了this,然后又赋值为空?

答疑:在Vue源码中,activeWatcher 通常是通过栈结构来管理的,这里这样可以支持嵌套的依赖收集。而上述代码Watcher 类的 get 方法中,将activeWatcher 设置为当前的Watcher实例的原因是依赖收集过程中给需要知道当前的依赖是谁,从而在属性发生变化时可以通知到相关的 Watcher 实例进行更新。在依赖收集完成后,将activeWatcher 设置为空的原因时为了防止在非依赖收集的情况下,误操作导致activeWatcher 保留了值。

一般来说,在Vue的相应式系统中,activeWatcher 在以下几种情况下会被设置为某个具体的 Watcher 对象:

  • 组件渲染过程中:在组件的渲染过程中,Vue会创建一个Watcher对象来实现观察组件的渲染函数。此时activeWatcher 会被设置为这个渲染Watcher对象,以便在渲染函数中访问组件的响应式数据时进行依赖收集
  • 计算属性或者侦听的求职过程中:当计算属性或者侦听器的值被求值时,Vue会创建一个Watcher对象来观察相关的响应式数据,以便在求值过程中访问相关的响应式数据时进行依赖收集。
  • 用户手动创建的Watcher

以上情况下,activeWatcher 都会在相应的Watcher对象的get方法中被设置为当前Watcher实例。在依赖收集完成后,activeWatcher 会被重新设置为null,以便下一次依赖收集的时候再次被设置为新的Watcher对象。

2.Watcher 类中的value的作用是什么?

答疑:在 Vue 的响应式系统中,Watcher 类负责观察数据的变化,value 的存在可以让 Watcher 在依赖收集时记录当前的值,在数据发生变化时,可以通过对比新旧值来判断是否需要触发更新操作。

相关文章:

源码的角度分析Vue2数据双向绑定原理

什么是双向绑定 我们先从单向绑定切入,其实单向绑定非常简单,就是把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新。那么双向绑定就可以从此联想到,即在单向绑定的基础上,用户更新…...

动态规划(算法竞赛、蓝桥杯)--树形DP树形背包

1、B站视频链接&#xff1a;E18 树形DP 树形背包_哔哩哔哩_bilibili #include <bits/stdc.h> using namespace std; const int N110; int n,V,p,root; int v[N],w[N]; int h[N],to[N],ne[N],tot; //邻接表 int f[N][N];void add(int a,int b){to[tot]b;ne[tot]h[a];h[a…...

electron打包前端项目

1.npm run build 打包项目文件到disk文件夹 2.安装electron:npm install electron 打开后进到/dist里面 然后把这个项目的地址配置环境变量 配置环境变量&#xff1a;在系统变量的path中添加进去 配置成功后&#xff0c;electron -v看看版本。 3.创建主程序的入口文件main.…...

2.1基本算法之枚举7647:余数相同问题

已知三个正整数 a&#xff0c;b&#xff0c;c。 现有一个大于1的整数x&#xff0c;将其作为除数分别除a&#xff0c;b&#xff0c;c&#xff0c;得到的余数相同。 请问满足上述条件的x的最小值是多少&#xff1f; 数据保证x有解 #include<bits/stdc.h>//万能头 using…...

求最短路径之迪杰斯特拉算法

对fill用法的介绍 1.用邻接矩阵实现 const int maxn100; const int INF100000000;//无穷大&#xff0c;用来初始化边 int G[maxn][maxn];//用邻接矩阵存储图的信息 int isin[maxn]{false};//记录是否已被访问 int minDis[maxn];//记录到顶点的最小距离void Dijkstra(int s,in…...

python大学社团管理系统开发文档

项目介绍 一直想做一款大学社团管理系统&#xff0c;看了很多优秀的开源项目但是发现没有合适的。于是利用空闲休息时间开始自己写了一套管理系统。 在线体验 代码下载&#xff1a;https://github.com/geeeeeeeek/python_team演示地址&#xff1a;http://team.gitapp.cn/ &…...

leetcode 1328.破坏回文串

题目链接LeetCode1328 1.题目 给你一个由小写英文字母组成的回文字符串 palindrome &#xff0c;请你将其中 一个 字符用任意小写英文字母替换&#xff0c;使得结果字符串的 字典序最小 &#xff0c;且 不是 回文串。 请你返回结果字符串。如果无法做到&#xff0c;则返回一个…...

重学SpringBoot3-自动配置机制

重学SpringBoot3-自动配置机制 引言Spring Boot 自动配置原理示例&#xff1a;Spring Boot Web 自动配置深入理解总结相关阅读 引言 Spring Boot 的自动配置是其最强大的特性之一&#xff0c;它允许开发者通过最少的配置实现应用程序的快速开发和部署。这一切都得益于 Spring …...

sql基本语法+实验实践

sql语法 注释&#xff1a; 单行 --注释内容# 注释内容多行 /* 注释内容 */数据定义语言DDL 查询所有数据库 show databases;注意是databases而不是database。 查询当前数据库 select database();创建数据库 create database [if not exists] 数据库名 [default charset 字符…...

Node.js中的并发和多线程处理

在Node.js中&#xff0c;处理并发和多线程是一个非常重要的话题。由于Node.js是单线程的&#xff0c;这意味着它在任何给定时间内只能执行一个任务。然而&#xff0c;Node.js的事件驱动和非阻塞I/O模型使得处理并发和多线程变得更加高效和简单。在本文中&#xff0c;我们将探讨…...

node.js 封装分页查询

node.js封装sql分页查询 方法&#xff1a; /*** 生成分页查询sql* param {string} table 表名* param {number} pageNum 分页页数 * param {number} pageSize 分页条数 * param {object} query 查询对象 例&#xff1a;{id:1,name:小明}* returns sql语句*/ const limit (ta…...

iptables 基本使用

iptables 主要用到两个表&#xff1a;filter 和 nat&#xff0c;其中 filter 表可以用来过滤数据包&#xff1b;nat 可以用来修改数据包的源地址和目的地址。 chain chain 是 table 中对数据包进行匹配的规则&#xff0c;对于 filter 来说 chain 有 INPUT & OUTPUT & …...

食品笔记()

吃东西有时不注意&#xff0c;就容易不舒服&#xff0c;记录下。 辣椒 辣椒真是个让人又爱又恨的东西。 看着想吃&#xff0c;吃着过瘾&#xff0c;吃完容易肚子疼。 主要是这东西本身就会刺激身体&#xff0c;即使是能吃辣的人&#xff0c;也容易造成肠胃发炎。 适量吃些即…...

C++入门和基础

目录 文章目录 前言 一、C关键字 二、命名空间 2.1 命名空间的定义 2.2 命名空间的使用 2.3 标准命名空间 三、C输入&输出 四、缺省参数 4.1 缺省参数的概念 4.2 缺省参数的分类 五、函数重载 5.1 函数重载的简介 5.2 函数重载的分类 六、引用 6.1 引用的…...

一些C语言知识

C语言的内置类型&#xff1a; char short int long float double C99中引入了bool类型&#xff0c;用来表示真假的变量类型&#xff0c;包含true&#xff0c;false。 这个代码的执行结果是什么&#xff1f;好好想想哦&#xff0c;坑挺多的。 #include <stdio.h>int mai…...

代码工具APEX的入门使用(未包含安装)

第一次使用APEX是2019年&#xff0c;这个技术成名已久只是我了解的比较晚。请看Oracle ACE的网站&#xff0c;这就是用APEX做的。实际上有一次我看O记的人操作他们的办公流程&#xff0c;都是用APEX做的。 那一年&#xff0c;我用APEX做了一个CMDB的管理系统。那时候还没有流行…...

负载均衡.

简介: 将请求/数据【均匀】分摊到多个操作单元上执行&#xff0c;负载均衡的关键在于【均匀】。 负载均衡的分类: 网络通信分类 四层负载均衡:基于 IP 地址和端口进行请求的转发。七层负载均衡:根据访问用户的 HTTP 请求头、URL 信息将请求转发到特定的主机。 载体维度分类 硬…...

Git 指令深入浅出【2】—— 分支管理

Git 指令深入浅出【2】—— 分支管理 分支管理1. 常用分支管理指令2. 合并分支合并冲突合并模式 3. 实战演习 分支管理 1. 常用分支管理指令 # 查看本地分支 git branch# 查看远程分支 git branch -r# 查看全部分支 git branch -aHEAD 指向的才是当前的工作分支 # 查看当前分…...

工作流/任务卸载相关开源论文分享

decima-sim 概述&#xff1a; 图神经网络强化学习处理多工作流 用的spark的仿真环境&#xff0c;mit的论文&#xff0c;价值很高&#xff0c;高被引&#xff1a;663仓库地址&#xff1a;https://github.com/hongzimao/decima-sim论文&#xff1a;https://web.mit.edu/decima/co…...

为什么要用Python?

为什么要用Python&#xff1f; Python简单易用&#xff1a;提供大量的简单易用数据结构和内置库&#xff0c;语法结构也很简单易读&#xff0c;不需要使用括号来进行代码块分组&#xff0c;也不需要预声明变量或参数。Python开发效率高&#xff1a;简单易用的前提下&#xff0…...

不升级系统也能用VSCode远程开发:老版本Linux的glibc兼容方案大全

老版本Linux系统下VSCode远程开发的五大兼容方案 在企业开发环境中&#xff0c;生产服务器往往运行着CentOS 7或Ubuntu 18.04等长期支持版本&#xff0c;这些系统的glibc库版本可能无法满足最新VSCode远程开发组件的需求。本文将深入探讨五种无需升级系统即可解决glibc兼容性问…...

第八章:实战项目案例

第八章&#xff1a;实战项目案例 8.1 项目一&#xff1a;Todo 应用&#xff08;Vue 3 Pinia&#xff09; 项目初始化 npm create vitelatest todo-app -- --template vue cd todo-app npm install pinia npm install -D vitejs/plugin-vue项目结构 todo-app/ ├── src/ …...

文件 IO

目录 一. 文件 1.1 文件的概念 1.2 文件目录 1.3 文件路径 1.3.1 绝对路径 1.3.2 相对路径 1.4 文件的类型 二. IO 2.1 文件系统操作-- File类 2.1.1 File类的构造方法 2.1.2 File类中的方法 2.2 文件内容操作 2.2.1 字节流 2.2.1.1 InputStream中的方法 2.2.1.2…...

手把手教你用B站NFT工具设置小钻石头像(含最新工具下载与使用指南)

手把手教你用B站NFT工具设置小钻石头像&#xff08;含最新工具下载与使用指南&#xff09; 在数字藏品风靡的当下&#xff0c;B站推出的NFT小钻石头像成为了许多用户展示个性的新选择。不同于传统的头像设置&#xff0c;NFT头像不仅具有独特的收藏价值&#xff0c;还能在B站社…...

非开发者终于拥有了自己的“后台 AI 代理”

一位财务总监每周一都要花 90 分钟手动清洗 150 行 Excel 数据&#xff1a;去重、统一日期格式、把文字评级转成数字。 他明明知道 AI 能帮忙&#xff0c;却只在 Perplexity 里敲一句“帮我分析这份数据”&#xff0c;得到一段总结后就关掉标签&#xff0c;继续手动操作。 开发…...

【MATLAB源码-第320期】基于matlab的混沌序列图像加密解密系统仿真,测试加解密速度、资源占用、模糊攻击测试、密钥空间和敏感性分析。

操作环境&#xff1a;MATLAB 2024a1、算法描述基于混沌序列的图像加密 与解密系统 随着数字信息技术的迅速发展&#xff0c;图像作为信息载体的使用越来越广泛。然而&#xff0c;图像数据在传输和存储过程中容易受到窃取、篡改或破坏&#xff0c;因此确保图像数据的安全性显得尤…...

解放词库自由:跨平台词库迁移的全场景解决方案

解放词库自由&#xff1a;跨平台词库迁移的全场景解决方案 【免费下载链接】imewlconverter ”深蓝词库转换“ 一款开源免费的输入法词库转换程序 项目地址: https://gitcode.com/gh_mirrors/im/imewlconverter 当你更换新手机时&#xff0c;是否曾为输入法里积累多年的…...

Qwen-Image-Edit保姆级教程:3步搭建本地修图神器,隐私安全有保障

Qwen-Image-Edit保姆级教程&#xff1a;3步搭建本地修图神器&#xff0c;隐私安全有保障 想要一款既能保护隐私又能快速修图的AI工具&#xff1f;今天给大家介绍基于阿里通义千问Qwen-Image-Edit模型的本地化修图方案&#xff0c;无需联网、数据不出本地&#xff0c;3步就能搭…...

智能家居中枢:OpenClaw桥接Qwen3.5-9B实现语音控制图片检索

智能家居中枢&#xff1a;OpenClaw桥接Qwen3.5-9B实现语音控制图片检索 1. 为什么需要智能家居中的图片检索 每次家庭聚会后整理照片都让我头疼。手机相册里堆积着上千张照片&#xff0c;当亲友们围坐在客厅想回顾某次旅行时&#xff0c;"找那张在雪山前跳起来的合影&qu…...

手把手教你理解机器人阻抗控制:阻尼-弹簧-质量模型详解

机器人阻抗控制实战&#xff1a;从阻尼-弹簧-质量模型到智能柔顺操作 当机械臂需要完成插拔USB接口这样的精细操作时&#xff0c;纯位置控制的局限性立刻显现——哪怕0.1毫米的误差都可能导致接口损坏。这正是阻抗控制技术大显身手的场景&#xff1a;通过模拟弹簧的柔顺特性&am…...