vue双向绑定原理

news2024/11/25 11:26:45

Vue双向绑定的原理

vue的双向绑定原理:vue数据的双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。其核心就是通过obj.defineProperty()方法来实现数据的劫持,在数据变化时发布消息给订阅者,触发相应的监听回调。也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;


一、MVVM模式


MVVM(Model-View-ViewModel)是对 MVC(Model-View-Control)和 MVP(Model-View-Presenter)的进一步改进。

View:视图层(UI 用户界面)
ViewModel:业务逻辑层(一切 js 可视为业务逻辑,也就是前端的日常工作)
Model:数据层(存储数据及对数据的处理如增删改查)

  •  MVVM 将数据双向绑定(data-binding)作为核心思想,View 和 Model 之间没有联系,它们通过 ViewModel

        这个桥梁进行交互

  • Model 和 ViewModel 之间的交互是双向的,因此 View 的变化会自动同步到 Model,而 Model

        的变化也会立即反映到 View 上显示

  • 当用户操作 View,ViewModel 感知到变化,然后通知 Model 发生相应改变;反之当 Model 发生改变,ViewModel

        也能感知到变化,使 View 作出相应更新

  • MVVM框架的的核心就是双向绑定, 其原理是通过数据劫持+发布订阅模式相结合的方式来是实现的,简单来说就是数据层发生变化的时候,可同布更新视图层,当视图层发生变化的时候,同步更新数据层

二、双向绑定的核心: Object.defineProperty()


Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

obj:要定义属性的对象
prop:要定义或修改的属性的名称或 Symbol
descriptor:要定义或修改的属性描述符
返回值:被传递给函数的对象
我们通过Object.defineProperty的get方法用来获取值 set方法用来拦截设置值

  var obj = {};  //定义一个空对象
    Object.defineProperty(obj, 'val', {//定义要修改对象的属性
        get: function () {
            console.log('获取对象的值')
        },
        set: function (newVal) { 
            console.log('设置对象的值:最新的值是'+newVal);
        }
    });
    obj.hello = 'hello world'

js通过Object.defineProperty方法简单的实现双向绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="text" id="app">
    <span id="childSpan"></span>
</body>
<script>
    var obj = {}
    var initValue='初始值'
    Object.defineProperty(obj,'initValue',{
        get(){
            console.log('获取obj最新的值');
            return initValue
        },
        set(newVal){
            initValue = newVal
            console.log('设置最新的值');
            // 获取到最新的值  然后将最新的值赋值给我们的span
            document.getElementById('childSpan').innerHTML = initValue
            console.log(obj.initValue);
        }
    })
    document.addEventListener('keyup', function (e) {
        obj.initValue = e.target.value; //监听文本框里面的值 获取最新的值 然后赋值给obj 
    })
    
</script>
</html>

三、实现双向绑定的过程

在这里插入图片描述

任务拆分:

  1. 将vue实例中的数据渲染到页面上
  2. 将页面上的数据变更同步到vue实例中
  3. vue实例中data数据变更 页面上数据同步变更

传统的js来操作dom是非常繁琐的 性能及低的 比如我们要操作 dom 10次 操作第一次的时候 浏览器并不知道后面还有9次操作 所以浏览器会进行10次的重绘重排 但有的时候我们进行下一次的操作的时候 前一次的操作结果已经不准确了 那前一次的操作结果就是无用功了 白白浪费了性能。 DocuemntFragment(碎片化文档)可以把其看成一个容器 把浏览器的10次操作都扔到这个容器里 最终把最后一次的结果输出到浏览器上 这样我们页面只渲染了一次 ,并且DocuemntFragment是在内存中执行的 效率非常高。

DocuemntFragment 拦截数据:、

 function nodeToFragment(node){
        var fragment = document.createDocumentFragment();
        var child = null;
        while(child = node.firstChild){
            fragment.appendChild(child)
            
        }
        return fragment
    }

实现思路:

  1. 如何将vue data 中的数据对应的绑定到文本上
  2. 如果将input中的数据 更新到vue实例的data中

nodeToFragment方法中 我们会拦截到所有的dom 然后对dom节点的属性进行分析 比如找到v-model中的对应的变量跟vue data中的变量进行匹配,匹配到对应项,然后进行更新数据

vue构造函数
 

	// 构造函数
    function Vue(options){
        this.data = options.data
        var id = options.el
        var dom = nodeToFragment(document.getElementById(id),this)
        document.getElementById(id).appendChild(dom)
    }
    // 实例
    var vm = new Vue({
         el: 'app',
         data: {
             text: '赵刚',
             test:'测试',
             name:'hbb'
         }
     })
     // dom结构
     <div id="app">
         测试双向绑定demo
         <input type="text" v-model="text" /> {{text}}
     </div>

 拦截dom并找到vue实例中data对应的数据,然后渲染到页面上

        //编译函数
        function compile(node, vm) {
            var reg = /\{\{(.*)\}\}/; // 来匹配{{xxx}}中的xxx
            //如果是元素节点
            if(node.nodeType === 1) {
                var attr = node.attributes;
                //解析元素节点的所有属性
                for(let i = 0; i < attr.length; i++) {
                    if(attr[i].nodeName == 'v-model') {
                        var name = attr[i].nodeValue //看看是与哪一个数据相关
                        node.addEventListener('input', function(e) { //将与其相关的数据改为最新值
                            vm[name] = e.target.value
                        })
                        node.value = vm.data[name]; //将data中的值赋予给该node
                    }
                }
            }

            //如果是文本节点
            if(node.nodeType === 3) {
                if(reg.test(node.nodeValue)) {
                    var name = RegExp.$1; //获取到匹配的字符串
                    name = name.trim();
                    node.nodeValue = vm[name]; //将data中的值赋予给该node
                }
            }
        }

将获取到的data中的数据更新到文档碎片中

	   function nodeToFragment(node, vm) {
            var fragment = document.createDocumentFragment();
            var child;
            while(child = node.firstChild) {
                compile(child, vm);// 将从data中获取到的数据的dom更新到文档碎片中  这样页面上的dom就有值啦
                fragment.appendChild(child);
            }
            return fragment
        }

如此我们就完成了第一步 将data中数据渲染到页面上啦

第二步是当页面上的数据发生变化的时候 将最新的数据更新到data中
首先我们需要拦截data对象中的所有属性 这样当页面数据发生改变 我们会在setter函数中监听到数据变化并拿到最新的数据

		function Vue(options) {
            this.data = options.data;
            observe(this.data, this) //观察整个data对象
            var id = options.el;
            var dom = nodeToFragment(document.getElementById(id), this)
            //处理完所有节点后,重新把内容添加回去
            document.getElementById(id).appendChild(dom)
        }
        // 拦截data中的所有属性
		function observe(obj, vm) {
            for(let key of Object.keys(obj)) {
                defineReactive(vm, key, obj[key]);
            }
        }
        function defineReactive(obj, key, val) {
            Object.defineProperty(obj, key, {
                get: function() {
                	// 获取对象的值
                    return val
                },
                set: function(newVal) {
                    val = newVal;
                    // 当对象属性值变更 拦截数据
                    console.log('新值' + val);
                }
            })
        }

第三步 就是当我们已经拿到最新的变更后的数据了 那么怎么通知dom 让其对应的更新成最新的数据呢 我们都知道一个页面就是一个组件 一个vue实例对象 那么我们就需要一个中间桥梁 当数据发生变化 这个中间桥梁拿到最新数据 然后告诉页面 数据更新了 你需要重新渲染了

四、发布订阅模式:


发布订阅者模式就是一种一对多的依赖关系。多个订阅者(一般是注册的函数)同时监听同一个数据对象,当这个数据对象发生变化的时候会执行一个发布事件,通过这个发布事件会通知到所有的订阅者,使它们能够自己改变对数据对象依赖的部分状态。
一个完整的订阅发布模式,由发布者、订阅者、消息管理器三部分组成
在这里插入图片描述

 在双向数据绑定中 每当有数据发生变化就要发布一个通知 让视图层更新 那么在set函数中就要发布订阅函数 而每一个对象属性都是订阅者

        //dep构造函数
        function Dep() {
            this.subs = [] // 观察主题添加订阅者
        }
        Dep.prototype = {
            // 添加订阅者
            addSub(sub) {
                this.subs.push(sub)
            },
            // 发布通知
            notify() {
                this.subs.forEach(function(sub) {
                    sub.update();
                })
            }
        }
        function defineReactive(obj, key, val) {
            var dep = new Dep(); //观察者实例
            Object.defineProperty(obj, key, {
                get: function() {
                    if(Dep.target) { //每一个观察着都是唯一的
                        dep.addSub(Dep.target)
                    }
                    return val
                },
                set: function(newVal) {
                    if(newVal === val) {
                        return
                    }
                    val = newVal;
                    console.log('新值' + val);
                    //一旦更新立马通知
                    dep.notify();
                }
            })
        }

以上我们已经完成了当数据发生变化的时候,通知所有的订阅者 数据更新了 快更新dom吧 那如何将发布者和订阅者关联起来呢?通过将每一个Watcher实例赋值给Dep.target 的全局变量,这样Watcher和Dep就有关系了,当操作完成了就需要将Dep.target 置为空 这样保证了 Watcher实例的唯一性
 

        // Watcher监听者
        function Watcher(vm, node, name) {
            Dep.target = this;
            this.vm = vm;
            this.node = node;
            this.name = name;
            this.update();
            Dep.target = null;
        }

        Watcher.prototype = {
            update() {
                this.get();
                this.node.nodeValue = this.value //更改节点内容的关键
            },
            get() {
                this.value = this.vm[this.name] //触发相应的get
            }
        }

如此 双向绑定原理已经介绍完成,虽然刚开始接触,还有那么一丢丢小困难,但是困难是暂时的,只要我们踏踏实实的一步一步的往下去,突然有一天你会豁然开朗的发现,原来也不过如此嘛

最终,附上完整版的demo实例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <div id="app">
            测试双向绑定demo
            <input type="text" v-model="text" /> {{text}}
        </div>
    </body>
    <script type="text/javascript">
        //编译函数
        function compile(node, vm) {
            var reg = /\{\{(.*)\}\}/; // 来匹配{{xxx}}中的xxx
            //如果是元素节点
            if(node.nodeType === 1) {
                var attr = node.attributes;
                //解析元素节点的所有属性
                for(let i = 0; i < attr.length; i++) {
                    if(attr[i].nodeName == 'v-model') {
                        var name = attr[i].nodeValue //看看是与哪一个数据相关
                        node.addEventListener('input', function(e) { //将与其相关的数据改为最新值
                            vm[name] = e.target.value
                        })
                        node.value = vm.data[name]; //将data中的值赋予给该node
                        node.removeAttribute('v-model')
                    }
                }
            }

            //如果是文本节点
            if(node.nodeType === 3) {
                if(reg.test(node.nodeValue)) {
                    var name = RegExp.$1; //获取到匹配的字符串
                    name = name.trim();
                    node.nodeValue = vm[name]; //将data中的值赋予给该node
                    new Watcher(vm, node, name) //绑定一个订阅者
                }
            }
        }

        // 在向碎片化文档中添加节点时,每个节点都处理一下
        function nodeToFragment(node, vm) {
            var fragment = document.createDocumentFragment();
            var child;
            while(child = node.firstChild) {
                compile(child, vm);
                fragment.appendChild(child);
            }
            return fragment
        }

        //  Vue构造函数     
        //   观察data中的所有属性值,注意增添了observe
        function Vue(options) {
            this.data = options.data;
            observe(this.data, this)
            var id = options.el;
            var dom = nodeToFragment(document.getElementById(id), this)
            //处理完所有节点后,重新把内容添加回去
            document.getElementById(id).appendChild(dom)
        }

        //实现一个响应式监听属性的函数。一旦有赋新值就发生变化 
        function defineReactive(obj, key, val) {
            var dep = new Dep(); //观察者实例
            Object.defineProperty(obj, key, {
                get: function() {
                    if(Dep.target) { //每一个观察着都是唯一的
                        dep.addSub(Dep.target)
                    }
                    return val
                },
                set: function(newVal) {
                    if(newVal === val) {
                        return
                    }
                    val = newVal;
                    console.log('新值' + val);
                    //一旦更新立马通知
                    dep.notify();
                }
            })
        }

        //实现一个观察者,对于一个实例 每一个属性值都进行观察。
        function observe(obj, vm) {
            for(let key of Object.keys(obj)) {
                defineReactive(vm, key, obj[key]);
            }
        }

        // Watcher监听者
        function Watcher(vm, node, name) {
            Dep.target = this;
            this.vm = vm;
            this.node = node;
            this.name = name;
            this.update();
            Dep.target = null;
        }

        Watcher.prototype = {
            update() {
                this.get();
                this.node.nodeValue = this.value //更改节点内容的关键
            },
            get() {
                this.value = this.vm[this.name] //触发相应的get
            }
        }

        //dep构造函数
        function Dep() {
            this.subs = [] // 观察主题添加订阅者
        }
        Dep.prototype = {
            // 添加订阅者
            addSub(sub) {
                this.subs.push(sub)
            },
            // 发布通知
            notify() {
                this.subs.forEach(function(sub) {
                    sub.update();
                })
            }
        }

        var vm = new Vue({
            el: 'app',
            data: {
                text: '赵刚'
            }
        })
    </script>

</html>

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

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

相关文章

windows安装Stable Diffusion WebUI及问题解决记录

本文将详细介绍stable diffusion webui的下载、安装及问题解决。 StableDiffusion是2022年发布的深度学习文本到图像生成模型。它主要用于根据文本的描述产生详细图像&#xff0c;尽管它也可以应用于其他任务&#xff0c;如内补绘制、外补绘制&#xff0c;以及在提示词​&#…

AUTOSAR知识点Com(五):CANIf模式PUD Channel

1、概述 每个L-PDU分配给一个专用的物理CAN通道&#xff0c;该通道连接CAN控制器和CAN网络。通过这种方式&#xff0c;所有属于物理通道的L-PDU都可以在处理逻辑上&#xff0c;单一的L-PDU通道组上进行控制。这些逻辑组表示ECU连接到底层CAN网络的所有I-PDU。图 展示了L-PDU信道…

category排序专辑

case1——对有限类型的字段按指定要求排序&#xff1a; #学历分布 xueli_tsdf.pivot_table(index学历,values教师id,aggfunccount,marginsTrue,margins_name总计) xueli_ts[占比]np.round(xueli_ts[教师id]/xueli_ts.loc[总计,教师id],2) xueli_ts.reset_index(inplaceTrue)xu…

Adding Conditional Control to Text-to-Image Diffusion Models

安全验证 - 知乎知乎&#xff0c;中文互联网高质量的问答社区和创作者聚集的原创内容平台&#xff0c;于 2011 年 1 月正式上线&#xff0c;以「让人们更好的分享知识、经验和见解&#xff0c;找到自己的解答」为品牌使命。知乎凭借认真、专业、友善的社区氛围、独特的产品机制…

Java要学到什么程度才能找工作?小白怎么去面试Java岗位?

作为一个Java初学者&#xff0c;到底要学哪些知识点才能找到月薪过万的工作&#xff1f;或者说&#xff0c;怎样才能适应企业的开发流程&#xff0c;不至于进了公司都不知道怎么把需求转换成代码。甚至&#xff0c;唯唯诺诺的加班&#xff0c;一点自信都没有。本期文章&#xf…

STM32开发(18)----CubeMX配置RTC

CubeMX配置RTC前言一、什么是RTC&#xff1f;RTC时钟源RTC备份域二、实验过程1.CubeMX配置2.代码实现3.实验结果总结前言 本章介绍使用STM32CubeMX对RTC进行配置的方法&#xff0c;RTC的原理、概念和特点&#xff0c;配置各个步骤的功能&#xff0c;并通过实验方式验证。 一、…

Linux常见漏洞修复

一、nginx 修复TLS1.0&#xff0c;TLS1.1协议漏洞 1、漏洞描述 服务端口漏洞名称加固建议nginx443TLS版本1.0协议检测启用对TLS 1.2或1.3的支持&#xff0c;并禁用对TLS 1.0的支持。nginx443TLS版本1.1协议检测启用对TLS 1.2或1.3的支持&#xff0c;并禁用对TLS 1.1的支持。 …

Linux27 -- 通过抓包观察三次握手和四次挥手、链接的状态(tcp状态转移图)、TIME_WAIT 存在的原因

tcp协议的特点&#xff1a; 面向连接的&#xff0c;可靠的&#xff0c;流式服务 //面试常问&#xff1a; 一、通过抓包观察三次握手、四次挥手 工具&#xff1a;tcpdump 命令 抓冲我到他从他到我的数据包。 需要管理员权限。 运行示例&#xff1a; 进入管理员权限&#xff…

使用Advanced Installer打包程序及运行环境

Advanced Installer 工具版本&#xff1a;20.1.1 设置产品信息 选中右侧【Product Details】输入产品信息 设置文件和文件夹 添加使用VS发布之后的程序文件夹 设置文件夹刷新 选中文件夹&#xff0c;右键选择属性&#xff0c;选中Synchronize标签。启用“Synchronize conten…

InfluxDB 2 介绍与使用 flux查询 数据可视化

一、关键概念 相比V1 移除了database 和 RP&#xff0c;增加了bucket。 V2具有以下几个概念&#xff1a; timestamp、field key、field value、field set、tag key、tag value、tag set、measurement、series、point、bucket、bucket schema、organization 新增的概念&…

微服务 分布式搜索引擎 Elastic Search RestAPI

文章目录⛄引言一、RestAPI⛅导入数据⏰mapping映射分析⚡初始化RestClient二、索引库操作⌚创建索引库✒️删除索引库⚡判断索引库是否存在⛵小结⛄引言 本文参考黑马 分布式Elastic search Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#x…

synchronized 与 volatile 关键字

目录1.前言1.synchronized 关键字1. 互斥2.保证内存可见性3.可重入2. volatile 关键字1.保证内存可见性2.无法保证原子性3.synchronized 与 volatile 的区别1.前言 synchronized关键字和volatile是大家在Java多线程学习时接触的两个关键字&#xff0c;很多同学可能学习完就忘记…

QSGS四参数随机生长2D软件 quartet structure generation set

软件简介 AbyssFish四参数随机生长2D软件采用四参数随机生长算法quartet structure generation set (QSGS)&#xff0c;可用于构建二维随机孔隙图。 软件提供图片长度、宽度&#xff1b;随机生长算法的分布概率、生长概率、概率密度&#xff08;暂不考虑多相材料相互作用&…

可视化项目管理,控制项目进度,项目经理需要做好以下工作

对于项目的管理者来说&#xff0c;项目信息透明&#xff0c;能够更容易让管理者发现项目中的问题&#xff0c;及时找到问题的原因和相关任务的责任人。 当项目信息能相对精准地呈现给管理者时&#xff0c;也能促进项目成员也能更加认真负责的完成任务&#xff0c;不会找借口推…

Elasticsearch使用——中级篇

在上一篇&#xff0c;已经导入了大量数据到elasticsearch中&#xff0c;实现了elasticsearch的数据存储功能。但elasticsearch最擅长的还是搜索和数据分析。本篇&#xff0c;研究下elasticsearch的数据搜索功能。分别使用DSL和RestClient实现搜索。1.DSL查询文档elasticsearch的…

电子技术——功率耗散

电子技术——功率耗散 如今许多集成电路系统都是电池供电的&#xff0c;对于功率耗散限制很严格。其他高性能电路&#xff0c;例如计算机服务器机房产品&#xff0c;有着严格的热耗散功率限制。所以&#xff0c;减小IC中的功率耗散变成了IC设计中最重要的挑战性的设计。 本节…

层次聚类:BIRCH 聚类、Lance–Williams equation

前言 如果你对这篇文章感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 BIRCH 聚类 要求数据为向量形式&#xff0c;则通过构建 CF-tree (Clustering Feature Tree) 实现可扩展地高效聚类&#x…

嵌入式linux物联网毕业设计项目智能语音识别基于stm32mp157开发板

stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器&#xff0c;集成2个Cortex-A7核和1个Cortex-M4 核&#xff0c;A7核上可以跑Linux操作系统&#xff0c;M4核上可以跑FreeRT…

数据结构之二叉树(上)

文章目录前言一、二叉树的定义二、二叉树的几种情况三、特殊的二叉树1. 满二叉树2. 完全二叉树四、二叉树的存储结构1. 顺序存储2. 链式存储五、二叉树的性质总结前言 本文主要介绍了二叉树的基本概念以及二叉树的存储结构 一、二叉树的定义 一棵二叉树是结点的一个有限集合&…

Fortinet 发布《2022下半年度全球威胁态势研究报告》,七大发现值得关注!

全球网络与安全融合领域领导者Fortinet&#xff08;NASDAQ&#xff1a;FTNT&#xff09;&#xff0c;近日发布《2022 下半年度全球威胁态势研究报告》。报告指出&#xff0c;相对于组织攻击面的不断扩大以及全球威胁态势的持续演进&#xff0c;网络犯罪分子设计、优化技术与战术…