双向数据绑定详细解析(超详细)

news2024/9/28 13:28:47

在这里插入图片描述


文章目录

  • 一、什么是双向绑定
  • 二、双向绑定的原理是什么
    • 理解ViewModel
  • 三、实现双向绑定
    • 实现
    • 编译Compile
    • 依赖收集
  • 参考文献


一、什么是双向绑定

我们先从单向绑定切入单向绑定非常简单,就是把Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新双向绑定就很容易联想到了,在单向绑定的基础上,用户更新了ViewModel的数据也自动被更新了,这种情况就是双向绑定。举个栗子
在这里插入图片描述
当用户填写表单时,View的状态就被更新了,如果此时可以自动更新Model的状态,那就相当于我们把ModelView做了双向绑定关系图如下
在这里插入图片描述


二、双向绑定的原理是什么

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

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

而上面的这个分层的架构方案,可以用一个专业术语进行称呼:MVVM这里的控制层的核心功能便是 “数据双向绑定” 。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理

理解ViewModel

它的主要职责就是:

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

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

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

三、实现双向绑定

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

  • 1.new Vue()首先执行初始化,对data执行响应化处理,这个过程发生Observe
  • 2.同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile
  • 3.同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数
  • 4.由于data的某个key在⼀个视图中可能出现多次,所以每个key都需要⼀个管家Dep来管理多个Watcher
  • 5.将来data中数据⼀旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数

流程图如下:
在这里插入图片描述

实现

先来一个构造函数:执行初始化,对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 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]);  
    });  
  }  
}  

编译Compile

对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
在这里插入图片描述

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);  
  }  
} 

依赖收集

视图中会用到data中某key,这称为依赖。同⼀个key可能出现多次,每次都需要收集出来用⼀个Watcher来维护它们,此过程称为依赖收集多个Watcher需要⼀个Dep来管理,需要更新时由Dep统⼀通知
在这里插入图片描述
实现思路

  • 1.defineReactive时为每⼀个key创建⼀个Dep实例
  • 2.初始化视图时读取某个key,例如name1,创建⼀个watcher1
  • 3.由于触发name1getter方法,便将watcher1添加到name1对应的Dep
  • 4.当name1更新,setter触发时,便可通过对应Dep通知其管理所有Watcher更新
// 负责更新视图  
class Watcher {  
  constructor(vm, key, updater) {  
    this.vm = vm  
    this.key = key  
    this.updaterFn = updater  
  
    // 创建实例时,把当前实例指定到Dep.target静态属性上  
    Dep.target = this  
    // 读一下key,触发get  
    vm[key]  
    // 置空  
    Dep.target = null  
  }  
  
  // 未来执行dom更新函数,由dep调用的  
  update() {  
    this.updaterFn.call(this.vm, this.vm[this.key])  
  }  
} 

声明Dep

class Dep {  
  constructor() {  
    this.deps = [];  // 依赖管理  
  }  
  addDep(dep) {  
    this.deps.push(dep);  
  }  
  notify() {   
    this.deps.forEach((dep) => dep.update());  
  }  
}  

创建watcher时触发getter

class Watcher {  
  constructor(vm, key, updateFn) {  
    Dep.target = this;  
    this.vm[this.key];  
    Dep.target = null;  
  }  
}  

依赖收集,创建Dep实例

function defineReactive(obj, key, val) {  
  this.observe(val);  
  const dep = new Dep();  
  Object.defineProperty(obj, key, {  
    get() {  
      Dep.target && dep.addDep(Dep.target);// Dep.target也就是Watcher实例  
      return val;  
    },  
    set(newVal) {  
      if (newVal === val) return;  
      dep.notify(); // 通知dep执行更新方法  
    },  
  });  
} 

参考文献

  • https://www.liaoxuefeng.com/wiki/1022910821149312/1109527162256416
  • https://juejin.cn/post/6844903942254510087#heading-9

希望本文能够对您有所帮助!如果您有任何问题或建议,请随时在评论区留言联系 章挨踢(章IT)
谢谢阅读!

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

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

相关文章

永磁同步电机的磁场定向控制

目录 概述 通过系统仿真验证行为 探索模型架构 生成用于集成到嵌入式应用程序的控制器 C 代码 指定控制器模型的参考行为 创建 PIL 实现 准备用于 PIL 测试的控制器模型 测试生成的代码的行为和执行时间 结论 此示例说明从电机控制算法生成 C 代码并验证其编译行为和执…

AcWing 998. 起床困难综合症

原题链接 其实上面这一堆就是想说,输入 n,m以及 n 个数和该数所对应的运算,其中运算包括有 与、或、异或 三种,真正的问题就是在所有不大于 m 的数(非负数)中,对给定的 n 个数都按该数所对应的运算运算一遍…

GFS论文解读(一)——设计概述

介绍 在当今大数据时代,分布式文件系统已经成为处理海量数据的重要工具。而在这个领域中,「GFS(Google File System)」论文无疑是一篇具有里程碑意义的文献。GFS 由 Google 公司发表于 2003 年,它介绍了 Google 公司内…

C练习——肇事卡车车牌号

题目: 一辆卡车违反交通规则,撞人后逃跑。现场有3人目击事件,但没有记住车牌号,只记住了车号的一些特征。 甲说:“牌照前两位数字是相同的”,乙说:“牌照的后两位数字是相同的,但与…

centos通过yum安装redis

1. 安装yum添加epel源(此步根据环境,如果有源则可跳过,在阿里去可跳过) yum install epel-release 2 使用yum安装Redis yum install redis 出现如下图所示的内容 3 Redis配置 vim /etc/redis.conf :set number(显示行号) 61行&#x…

Flume基础知识(十一):Flume自定义接口

1)案例需求 使用 Flume 采集服务器本地日志,需要按照日志类型的不同,将不同种类的日志发往不同的分析系统。 2)需求分析 在实际的开发中,一台服务器产生的日志类型可能有很多种,不同类型的日志可能需要 发送…

在群晖NAS上搭建私有部署笔记软件——Blossom

一、Blossom 简介 Blossom 是一个需要私有部署的笔记软件,虽然本身定位是一个云端软件,但你仍然可以在本地部署,数据和图片都将保存在本地,不依赖任何的图床或者对象存储。 Blossom | Blossom (wangyunf.com)https://www.wangyun…

Zoho SalesIQ:提高品牌在社交媒体上参与度的实用指南

在当今快节奏的数字世界中,品牌参与度变得比以往任何时候都更加重要。社交媒体在企业与客户互动方面发挥着至关重要的作用,了解如何很好地利用社交媒体来增强品牌参与度至关重要。 正如我们在之前的博客中所了解到的,品牌参与是指在品牌与其…

企业数据库安全管理规范

1.目的 为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。 2.适用范围 本规范中所定义的数据管理内容,特指存放在信息系统数据库中的数据。 本…

原文件名自动编号方法:简单易懂的文件重命名规则

当处理大量文件时,例如图片、文档或其他类型的文件,经常要给它们重新命名,以便于管理和查找。一种常见的方法是使用自动编号,这样可以从001开始,一直编号到最后的文件。这种方法既简单又高效,且易于理解。下…

【读书】《白帽子讲web安全》个人笔记Ⅱ-1

目录 第二篇 客户端脚本安全 第2章 浏览器安全 2.1同源策略 2.2浏览器沙箱 2.3恶意网址拦截 2.4高速发展的浏览器安全 第二篇 客户端脚本安全 第2章 浏览器安全 近年来随着互联网的发展,人们发现浏览器才是互联网最大的入口,绝大多数用户使用互联…

UV机-理光G5六彩一白一光油配置

UV机-理光G5六彩一白一光油配置

【Navigation】global_planner 源码解析

全局规划器 global_planner 功能包 文章目录 global_planner 功能包结构1、plan_node.cpp2、planner_core.cpp3、astar.cpp4、dijkstra.cpp5、quadratic_calculator.cpp6、grid_path.cpp7、gradient_path.cpp8、orientation_filter.cpp全局规划大都基于静态地图进行规划,产生路…

加密的手机号如何模糊查询?

1 一次加载到内存 实现这个功能,我们第一个想到的办法可能是:把个人隐私数据一次性加载到内存中缓存起来,然后在内存中先解密,然后在代码中实现模糊搜索的功能。 这样做的好处是:实现起来比较简单,成本非常…

约数个数和约数之和算法总结

知识概览 约数个数 由算数基本定理 可得对于N的任何一个约数d,有 因为N的每一个约数和~的一种选法是一一对应的,根据乘法原理可得, 一个数的约数个数为 约数之和 一个数的约数之和公式为 多项式乘积的每一项为 正好对应的是一个数的每一个约…

计算机Java项目|Springboot疫情网课管理系统

项目编号:L-BS-ZXBS-07 一,环境介绍 语言环境:Java: jdk1.8 数据库:Mysql: mysql5.7 应用服务器:Tomcat: tomcat8.5.31 开发工具:IDEA或eclipse 二,项目简介 疫情网课也都将通过计算机…

jmeter线程组

特点:模拟用户,支持多用户操作;可以串行也可以并行 分类: setup线程组:初始化 类似于 unittest中的setupclass 普通线程组:字面意思 teardown线程组:环境恢复,后置处理

2.3 A VECTOR ADDITION KERNEL

我们现在使用矢量加法来说明CUDA C程序结构。矢量加法可以说是最简单的数据并行计算,是顺序编程中Hello World的并行等价物。在我们展示向量加法的内核代码之前,首先回顾传统的向量加法(主机代码)函数的工作原理是有帮助的。图2.5…

阿里云服务器Centos安装宝塔面板

阿里云服务器Centos安装宝塔面板 1 背景1.1 aliyun1.2 Linux 2 安装步骤2.0 环境配置2.1 安装前准备2.2 宝塔安装2.3 建站 3 centos常用命令3.1 防火墙相关 1 背景 1.1 aliyun 阿里云服务器是阿里云提供的一项云计算服务,它能够帮助用户快速搭建网站、应用和服务&…

专栏序言-GDB高级调试技巧实战

工欲善其事必先利其器! GDB耍的溜,BUG找的快! 在软件开发的世界里,调试是不可避免的。对于C/C开发者来说,GDB就是那把调试利器!如果你只知道单步调试,那定位问题的速度肯定是龟速&#xff1b…