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对象代理到$data
    this.$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身上所有key
    for (let key in this.$data) {
      // 数据代理给vue实例,this
      Object.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.value
            console.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 = this
      Object.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 = vm
    this.key = key
    this.node = node
    this.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.mfbz.cn/a/777988.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

35.哀家要长脑子了!--二分

模板 int check() {...} // 检查这个数是否符合相应的要求// 把区间[l, r] 划分成[l, mid] 和 [mid1, r] 时使用 // 找到数组中第一个大于等于某一值得元素或满足特定条件的第一个位置 int bsearch_1(int l, int r){int mid l r >> 1;while(l < r) {if(check(mi…

如何第一次从零上传项目到GitLab

嗨&#xff0c;我是兰若&#xff0c;今天想给大家说下&#xff0c;如何上传一个完整的项目到与LDAP集成的GitLab&#xff0c;也就是说这个项目之前是不在git上面的&#xff0c;这是第一次上传&#xff0c;这样上传上去之后&#xff0c;其他小伙伴就可以根据你这个项目的git地址…

linux 服务器数据备份 和 mysql 数据迁移

查看域名ip 查看程序所处文件位置 list open files 1、 lsof -i :port 查看端口获取进程 pid 2、lsof -i pid 1、scp 下载服务器文件到本地 security copy protocol 2、导出服务器 mysql 数据库&#xff08;表&#xff09;到本地 mysqldump是MySQL自带的一个实用程序&…

2024亚太杯数学建模竞赛(B题)的全面解析

你是否在寻找数学建模比赛的突破点&#xff1f;数学建模进阶思路&#xff01; 作为经验丰富的数学建模团队&#xff0c;我们将为你带来2024亚太杯数学建模竞赛&#xff08;B题&#xff09;的全面解析。这个解决方案包不仅包括完整的代码实现&#xff0c;还有详尽的建模过程和解…

Linux wget报未找到命令

wget报未找到命令需要安装wget 1、下载wget安装文件&#xff0c;本次于华为云资源镜像下载 地址&#xff1a;https://mirrors.huaweicloud.com/centos-vault/7.8.2003/os/x86_64/Packages/ 2、下载后上传到安装服务器/install_package&#xff0c;执行命令安装 rpm -ivh /i…

PD虚拟机怎么联网?PD虚拟机安装Win11无法上网 pd虚拟机连不上网怎么解决 mac安装windows虚拟机教程

PD虚拟机既可以联网使用&#xff0c;也可以单机使用。如需将PD虚拟机联网&#xff0c;可以共享Mac原生系统的网络&#xff0c;其使用体验与真实系统无异。本文会详细讲解PD虚拟机如何联网&#xff0c;并会进一步解决PD虚拟机安装Win10无法上网的问题。 如果有网络相关问题的小伙…

女生学计算机好不好?感觉计算机分有点高……?

众所周知&#xff0c;在国内的高校里&#xff0c;计算机专业的女生是非常少的&#xff0c;很多小班30人左右&#xff0c;但是每个班女生人数只有个位数。这就给很多人一个感觉&#xff0c;是不是女生天生就不适合学这个东西呢&#xff1f;女生是不是也应该放弃呢&#xff1f;当…

Deep Filtered Back Projection for CT Reconstruction

CT重建中的深度滤波反投影 论文链接&#xff1a;https://ieeexplore.ieee.org/document/10411896 项目链接&#xff1a; ABSTRACT 滤波反投影(FBP)是一种经典的计算机断层扫描(CT)重建解析算法&#xff0c;具有很高的计算效率。然而&#xff0c;用FBP重建的图像往往存在过多…

Http中get与post的区别,99%的人都理解错了吧

Get和Post是HTTP请求的两种基本方法&#xff0c;要说它们的区别&#xff0c;接触过WEB开发的人都能说出一二。 最直观的区别 就是Get把参数包含在URL中&#xff0c;Post通过request body传递参数。 你可能自己写过无数个Get和Post请求&#xff0c;或者已经看过很多权威网站总…

基于TCP的在线词典系统(分阶段实现)

1.功能说明 一共四个功能&#xff1a; 注册 登录 查询单词 查询历史记录 单词和解释保存在文件中&#xff0c;单词和解释只占一行, 一行最多300个字节&#xff0c;单词和解释之间至少有一个空格。 2.功能演示 3、分阶段完成各个功能 3.1 完成服务器和客户端的连接 servic…

【大数据】什么是数据融合(Data Fusion)?

目录 一、数据融合的定义 二、数据融合的类型 三、数据融合的挑战 四、数据融合的方法 五、数据融合的关键环节 1.数据质量监控指标的制定和跟踪 2.异常检测和处理机制 3.实时数据监测与反馈机制 4.协同合作与知识共享 一、数据融合的定义 数据融合&#xff08;Data Fusion&…

JVM原理(十三):JVM虚拟机类类加载器与双亲委派模型

1. 类加载器 Java虛拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到Java虚拟机外部去实现&#xff0c;以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(Class Loader)。 对于任意一…

利用级数公式计算圆周率(π)

π是是指圆的周长与直径的比值&#xff0c;是无限不循环小数&#xff0c;有很多种方法可以求得它的近似值。这里用比较容易实现的关于π的无穷级数来求它的前10000位的取值。 π / 2 π 具体的&#xff0c;用两个字符数组x,z分别存放当前计算得到的pi值&#xff0c;数组…

在5G/6G应用中实现高性能放大器的建模挑战

来源&#xff1a;Modelling Challenges for Enabling High Performance Amplifiers in 5G/6G Applications {第28届“集成电路和系统的混合设计”(Mixed Design of Integrated Circuits and Systems)国际会议论文集&#xff0c;2021年6月24日至26日&#xff0c;波兰洛迪} 本文讨…

【学术会议征稿】第四届机械自动化与电子信息工程国际学术会议(MAEIE 2024)

第四届机械自动化与电子信息工程国际学术会议&#xff08;MAEIE 2024&#xff09; 2024 4th International Conference on Mechanical Automation and Electronic Information Engineering 由安徽大学主办&#xff0c;安徽大学电气工程与自动化学院、安徽省人机共融系统与智能…

强化训练:day13(牛牛冲钻五、最长无重复子数组、重排字符串)

文章目录 前言1. 牛牛冲钻五1.1 题目描述1.2 解题思路1.3 代码实现 2. 最长无重复子数组2.1 题目描述2.2 解题思路2.3 代码实现 3. 重排字符串3.1 题目描述3.2 解题思路3.3 代码实现 总结 前言 1. 牛牛冲钻五   2. 最长无重复子数组   3. 重排字符串 1. 牛牛冲钻五 1.1 题…

[CTF]-PWN:House of Banana堆块题型综合分析

搭配largebin attack&#xff1a; 例题&#xff08;ISCC2024 heapheap)&#xff1a; 版本&#xff1a;glibc2.31 知识点&#xff1a;largebin attack、house of banana、uaf 查看保护 查看ida delete存在uaf漏洞 largebin attack手法&#xff1a; #创建4个堆块&#xff0…

Qtgui编程基础

Qt简介 ( 框架5.9.8版本 ) Qt是源代码级的跨平台一次编写到处编译.一次开发的Qt应用程序可以移值到不同平台. Qt体系架构 Qt的整个设计都是以单根继承为主这跟java相同.所谓单根继承就是说所有的Qt类都有一个共同的祖先都是QObject类QObject类后面有三个大的子类分别负责不同…

51单片机基础8——单片机控制超声波模块

超声波模块的使用 51单片机控制超声波模块1. 软硬件条件2. 超声波控制原理2.1 超声波测距原理2.2 超声波模块工作原理 3. 接线4. 代码实现 51单片机控制超声波模块 1. 软硬件条件 单片机型号&#xff1a;STC89C52RC开发环境&#xff1a;KEIL4烧录软件&#xff1a;stc-isp超声…

进程的初步认识

目录 一、硬件方面介绍 1.冯诺依曼体系结构 2.存储分级 二、软件 方面 1.操作系统是一款进行管理的软件&#xff0c;它可以管理硬件也可以管理软件 2.操作系统如何管理&#xff1f; 三、进程 1.概念 总结 四、linux中对进程的管理 1.task_ struct内容分类 2.查看进…