vue2响应式原理+模拟实现v-model

效果

简述原理

配置对象传入vue实例

模板解析,遍历出所有文本节点,利用正则替换插值表达式为真实数据

data数据代理给vue实例,以后通过this.xxx访问

给每个dom节点增加观察者实例,由观察者群组管理,内部每一个键值含有多个对不同dom的观察者

data数据劫持,给data的每个属性增加get和set函数,当值改变时触发观察者的update方法,更新所有与当前属性值相关的dom元素

劫持数据,说的挺好听的,就是加工数据嘛,多了set变化触发了模板重新渲染,该渲染方式使用观察者模式,获取观察者收集的各个dom的所有属性 div,观察的属性,div的属性textContent,同时根据最新值渲染模板

div.textContent=vm[key]

html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><!-- <script src="./vue.js"></script> -->
</head><body><div id="app">{{ name }} {{age}}<h1>{{age}}</h1><button @click="cli">按钮</button><input type="text" v-model="name"></div>
</body>
<script src="./vue.js">
</script>
<script>new Vue({el: '#app',data: {name: 'Zwwwww',age: 18,},methods: {cli() {console.log(this);console.log(this.age);}},})</script></html>

js代码

class Vue {constructor(options) {// 获取配置对象的节点,存放在vm$el身上this.$el = document.querySelector(options.el)// console.log(this.$el)// 将配置对象的data对象代理到$datathis.$data = options.data// 获取配置对象的method值,// vue实例监听,当触发了方法执行对应函数this.$methods = options.methods// 代理数据,后续通过this调用data对象的值this.$allWatcher = {}this.proxyData()// 劫持数据,为其增加观察者监视数据变化引起视图渲染this.observe()// 收集所有观察者,用对象的属性存放this.compile(this.$el)}// 数据代理到vue实例身上,后续this调用方法和data值proxyData() {// 遍历$data身上所有keyfor (let key in this.$data) {// 数据代理给vue实例,thisObject.defineProperty(this, key, {// 使用get和set后续触发获取值和设置值做额外操作get() {// 返回当前data对应的key属性值return this.$data[key]},set(value) {// 设置新值给当前属性this.$data[key] = value},})}}// js数据替换{{name}},模板解析compile(node) {// 遍历根节点下的所有节点node.childNodes.forEach((item, index) => {//递归元素节点,//如果还没到文本节点,也就是说元素节点内还有元素节点//则继续递归,直到元素节点没有子节点//第二种可能,如果为元素元素节点,判断是否有@click属性,并获取值//该值为绑定的methods方法if (item.nodeType === 1) {if (item.childNodes.length > 0) {this.compile(item)}if (item.hasAttribute('@click')) {let domKey = item.getAttribute('@click')// console.log('我是dom标签的key', domKey)// 设置监听器,如果被点击了,触发配置对象中的method函数item.addEventListener('click', () => {// 通过模板获取的属性值方法命,调用函数// 由于$methods只是引用地址,this指向还是原来的methods// 我们这里使用call来绑定他的上下文this,也就是绑定他的调用者// 在html部分我们就可以使用this.$data.age来获取vue实例上的数据// 如果我们想直接this.age 就需要将data代理到vue实例身上this.$methods[domKey.trim()].call(this)})}if (item.hasAttribute('v-model')) {let vmodelKey = item.getAttribute('v-model').trim()// console.log('我是v-model的key', vmodelKey)// 设置监听器,如果被点击了,触发配置对象中的method函数// 先单向给input框设置值item.value = this.$data[vmodelKey]item.addEventListener('input', () => {console.log('用户正在输入')// 每次输入时将输入框的值重新赋给data对象属性值,完成双向绑定this.$data[vmodelKey] = item.valueconsole.log(this.$data[vmodelKey])// 数据更新的同时重新解析模板// 这里使用观察者类观察数据变化所作出的响应})}}// 判断是否为文本节点,nodeType == 3// console.log(item.nodeType)// 如果是文本节点,进行数据替换// 如果不是文本节点,为元素节点则往里递归遍历文本节点if (item.nodeType === 3) {// 定义正则,替换{{xxx}}形式的字串为data下的属性值let reg = /\{\{(.*?)\}\}/g// 获取原本标签里的值,后续进行替换let text = item.textContent// console.log(text)item.textContent = text.replace(reg, (match, dataKey) => {// 先将dataKey去空格处理dataKey = dataKey.trim()// match为匹配到的整体,datakey为捕获到的子内容(.*?)//我们这里只需获取dataKey对应的值并塞入即可// console.log(match, dataKey)// 返回值作为替换内容 去除dataKey的前后空格// 增加观察者,传vue实例对象,data属性,item标签,标签属性// 相当于给每个文本节点都添加了一个观察者// 将所有观察者收集到vue实例上,在数据发生变化时调用观察者的update方法let watcher = new Watcher(this, dataKey, item, 'textContent')// 先进行判断观察者群组里是否有该节点的观察者// 如果有,就push添加,因为一个dataKey可能有多个模板使用// 举个例子,name属性可能在div1里使用也在div2里使用// 也就是将多个文本节点与同个datakey绑定if (this.$allWatcher[dataKey]) {this.$allWatcher[dataKey].push(watcher)}// 如果没有该属性的观察者存在,则新建空数组,push该观察者进入else {this.$allWatcher[dataKey] = []this.$allWatcher[dataKey].push(watcher)}return this.$data[dataKey]})}})}observe() {console.log('开始劫持')// 遍历所有的key,对其data数据劫持,值增加响应式功能for (let key in this.$data) {// 先获取value,否则数据重新定义后值会丢失// 此处的value变量不会随着observe方法的结束而销毁// 与内部匿名函数get和set作为闭包永远绑定在一起// 同时value值是对$data的一个引用,修改value值会引起$data变化let value = this.$data[key]// 保存一份vue的引用_this=this,// 防止后续在组件外部,也就是input输入框// 此时触发的set为一个闭包环境,上下文变成由defineproper定义的this.$data数据对象// 此时找不到vue实例作为上下文,对key和其他数据的引用也会失效let _this = thisObject.defineProperty(this.$data, key, {get() {console.log('有人要获取劫持数据值', value)// 返回上面存储的value值// 由于是响应式的,只有当观察到数据变化时所以才接触数据// 其value值作用域也作用在劫持过程中return value},set(newValue) {console.log('劫持到数据,修改值为', newValue)console.log('劫持前的数据为', value)value = newValue// 更新值的同时进行模板更新// 由于观察者队列含有观察者来观察不同属性管理的若干个模板// 调用该属性值下所有模板观察者即可,// 只要属性值变化,该属性值下的所有观察者重新渲染模板console.log(_this.$allWatcher)console.log(_this.$allWatcher[key])_this.$allWatcher[key].forEach((watcher, index) => {watcher.update()})},})}console.log('劫持成功')}
}class Watcher {constructor(vm, key, node, attr) {this.vm = vmthis.key = keythis.node = nodethis.attr = attr}//  item.textContent = this.$data[dataKey.trim()]update() {console.log('开始渲染')// 将原始dom标签内容值替换为 data里的属性值this.node[this.attr] = this.vm[this.key]}
}

代码参考

VUE双向绑定原理分析~实现视图和数据的双向绑定~_哔哩哔哩_bilibili

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

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

相关文章

sqlite 数据库 介绍

文章目录 前言一、什么是 SQLite &#xff1f;二、语法三、SQLite 场景四、磁盘文件 前言 下载 目前已经出到了&#xff0c; Version 3.46.0 SQLite&#xff0c;是一款轻型的数据库&#xff0c;是遵守ACID的关系型数据库管理系统&#xff0c;它包含在一个相对小的C库中。它是…

VMware虚拟机配置桥接网络

转载&#xff1a;虚拟机桥接网络配置 一、VMware三种网络连接方式 VMware提供了三种网络连接方式&#xff0c;VMnet0, VMnet1, Vmnet8&#xff0c;分别代表桥接&#xff0c;Host-only及NAT模式。在VMware的编辑-虚拟网络编辑器可看到对应三种连接方式的设置&#xff08;如下图…

Square Root SAM论文原理

文章目录 Square Root SAM论文原理核心原理SLAM问题的3种表示贝叶斯网络因子图&#xff08;Factor graph&#xff09;马尔科夫随机场(Markov Random Field, MRF) SLAM最小二乘问题&线性化因式分解 factorization矩阵与图(Matrices ⇔ Graphs)因式分解&变量消元(Factori…

Kafka系列之Kafka知识超强总结

一、Kafka简介 Kafka是什么 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff08;消息引擎系统&#xff09;&#xff0c;它可以处理消费者在网站中的所有动作流数据。 这种动作&#xff08;网页浏览&#xff0c; 搜索和其他用户的行动&#xff09;是在现代网络上的许多社…

14-22 剑和远方2 - 深度神经网络中的学习机制

概论 在第一部分中&#xff0c;我们深入探讨了人工智能的兴衰简史以及推动人工智能发展的努力。我们研究了一个简单的感知器&#xff0c;以了解其组件以及简单的 ANN 如何处理数据和权重层。在简单的 ANN 中&#xff0c;不会对数据执行特定操作。ANN 中的激活函数是一个线性函…

flask使用定时任务flask_apscheduler(APScheduler)

Flask-APScheduler描述: Flask-APScheduler 是一个 Flask 扩展&#xff0c;增加了对 APScheduler 的支持。 APScheduler 有三个内置的调度系统可供您使用&#xff1a; Cron 式调度&#xff08;可选开始/结束时间&#xff09; 基于间隔的执行&#xff08;以偶数间隔运行作业…

移动校园(7)ii:uniapp响应拦截器处理token,以及微信小程序报错当前页面正在处于跳转状态,请稍后再进行跳转....

依据昨天的写完&#xff0c;在token过期之后&#xff0c;再次调用接口&#xff0c;会触发后端拦截&#xff0c;扔进全局错误处理中间件 前端说明提示都没有&#xff0c;只有一个这个&#xff0c;现在优化一下&#xff0c;再写一个类似全局后置守卫&#xff0c;当状态码是401的时…

RAID 冗余磁盘阵列

RAID也是Linux操作系统中管理磁盘的一种方式。 只有Linux操作系统才支持LVM的磁盘管理方式。 而RAID是一种通用的管理磁盘的技术&#xff0c;使用于多种操作系统。 优势&#xff1a;提升数据的读写速度&#xff0c;提升数据的可靠性。具体实现哪什么功能&#xff0c;要看你所…

LVGL移植与VS模拟器使用

一、移植文件介绍 二、移植部分 第一步&#xff1a;创建LVGL文件夹 第二步&#xff1a; 构造LVGL文件夹&#xff1a;LVGL - GUI - lvgl - 第三步&#xff1a;添加文件 3.1 从examples中添加2个.c文件 3.2 从src中添加文件 draw文件 extra文件 第四步&#xff1a; 三、Ke…

Linux系统安装软件包的方法rpm和yum详解

起因&#xff1a; 本篇文章是记录学习Centos7的历程 关于rpm 常见命令 1&#xff09;查看已经安装的软件包 rpm -q 软件包名 2&#xff09;查看文件的相关信息 rpm -qi 软件包名 3&#xff09;查看软件包的依赖关系 就是说要想安装这个软件包&#xff0c;就必须把一些前…

三级_网络技术_04_中小型网络系统总体规划与设计

1.下列关于路由器技术特征的描述中&#xff0c;正确的是()。 吞吐量是指路由器的路由表容量 背板能力决定了路由器的吞吐量 语音、视频业务对延时抖动要求较低 突发处理能力是以最小帧间隔值来衡量的 2.下列关于路由器技术特征的描述中&#xff0c;正确的是()。 路由器的…

springboot公寓租赁系统-计算机毕业设计源码03822

摘要 1 绪论 1.1 研究背景与意义 1.2选题背景 1.3论文结构与章节安排 2 公寓租赁系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 系…

韦东山嵌入式linux系列-第一个实验

1 前言 笔者使用的是韦东山STM32MP157 Pro的板子&#xff0c;环境搭建部分按照说明文档配置完成。配置桥接网卡实现板子、windows、ubuntu的通信&#xff0c;也在开发板挂载 Ubuntu 的NFS目录 &#xff0c;这里就不再赘述了。 板子: 192.168.5.9 windows: 192.168.5.10 ubunt…

机械键盘如何挑选

机械键盘的选择是一个关键的决策&#xff0c;因为它直接影响到我们每天的打字体验。在选择机械键盘时&#xff0c;有几个关键因素需要考虑。首先是键盘的键轴类型。常见的键轴类型包括蓝轴、红轴、茶轴和黑轴等。不同的键轴类型具有不同的触发力、触发点和声音。蓝轴通常具有明…

聚类分析方法(一)

目录 一、聚类分析原理&#xff08;一&#xff09;聚类分析概述&#xff08;二&#xff09;聚类的数学定义&#xff08;三&#xff09;簇的常见类型&#xff08;四&#xff09;聚类框架及性能要求&#xff08;五&#xff09;簇的距离 二、划分聚类算法&#xff08;一&#xff0…

Java 有什么必看的书?

Java必看经典书有这两本&#xff1a; 1、Java核心技术速学版&#xff08;第3版&#xff09; 经典Java开发基础书CoreJava速学版本&#xff01;Java入门优选书籍&#xff0c;更新至Java17&#xff0c;内容皆是精华&#xff0c;让Java学习更简单&#xff0c;让Java知识应用更快速…

【Linux】什么是进程间通信?方式有哪些?本质理解?

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

NoSQL 之 Redis 集群部署

前言&#xff1a; &#xff08;1&#xff09;主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用 的。主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。缺陷&#xff1a; 故障…

vue3+antd 实现文件夹目录右键菜单功能

原本的目录结构&#xff1a; 右键菜单&#xff1a; 点击菜单以后会触发回调&#xff1a; 完整的前端代码&#xff1a; <template><a-directory-treev-model:expandedKeys"expandedKeys"v-model:selectedKeys"selectedKeys"multipleshow-li…

在 Docker 容器中运行 Vite 开发环境,有这两个问题要注意

容器化开发给我们带来了很多便捷&#xff0c;但是在开发环境下也有一些问题要注意&#xff0c;如果不解决这些问题&#xff0c;你的开发体验不会很好。 容器启动正常&#xff0c;却无法访问 我们用 Docker 启动一个 Vite Vue3 项目的开发环境后&#xff0c;发现端口日志一切…