vue3.0(十)双向数据绑定原理和v2.0对比

文章目录

  • MVVM框架
    • 1 理解ViewModel
    • 2 MVVM的优点
  • vue2.0 双向数据绑定原理
    • 1 实现双向数据绑定
    • 2 实现
    • 3 Vue2.0 缺点和解决办法
  • vue3.0 双向数据绑定原理
  • vue2.0和vue3.0 的差异
    • Vue2.0
    • Vue3.0
    • Object.defineProperty和Proxy的对比


MVVM框架

MVVM(Model-View-ViewModel)是一种软件架构模式,它将软件系统分为三个部分:模型(Model)、视图(View)和视图模型(ViewModel)。MVVM 模式的目标是将业务逻辑和用户界面分离,从而提高代码的可维护性和可测试性。
MVVM 中的三个缩写分别是 Model(模型)、View(视图)和 ViewModel(视图模型)。

  • Model:模型是指应用程序的数据模型,它包含了应用程序的业务逻辑和数据。
  • View:视图是指应用程序的用户界面,它负责显示模型中的数据。
  • ViewModel:视图模型是 MVVM 模式中的核心部分,它是一个中间层,负责将模型中的数据转换为视图可以显示的数据,并将用户在视图上的操作转换为对模型的操作。MVVM 模式的目标是将应用程序的业务逻辑和用户界面分离,从而提高应用程序的可维护性和可测试性。通过数据绑定机制将视图和模型进行关联。当模型发生变化时,视图会自动更新,从而实现了数据的双向绑定。
    在这里插入图片描述

1 理解ViewModel

主要职责:
- 数据变化后更新视图
- 视图变化后更新数据
有主要部分组成:
- 家庭起(Observer): 对所有数据的属性进行监听
- 解析器(Compiler):对每个元素节点的指令进扫描跟解析,根据指令模版一环数据,以及绑定相应的更新函数。

2 MVVM的优点

MVVM模式,主要目的是分离视图(View)和模型(Model),有几大优点
1. 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
3. 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,使用Expression Blend可以很容易设计界面并生成xaml代码。
4. 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。

vue2.0 双向数据绑定原理

Vue2.0实现MVVM(双向数据绑定)的原理是通过 Object.defineProperty 来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

1 实现双向数据绑定

  • new Vue()首先执行初始化,对 data执行响应化处理,这个过程发生 Observe中。
  • 同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中
  • 同时定义一个更新函数和W啊提车人,将来对应数据变化时Watcher会调用更新函数
  • 由于data的某个可以再一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个Watcher
  • 将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数
    流程图如下:
    在这里插入图片描述

2 实现

  1. 先创建一个构造函数,执行初始化,对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); 
     } 
    }
    
  2. 对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]); 
     }); 
     } 
    }
    
  3. 编译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); 
     } 
    }
    
  4. 依赖收集
    视图中会用到data中某key,成为依赖。同一个key可能出现多次,每次都需要收集出来用一个watcher来维护,依赖收集多个watcher需要一个Dep来管理,需要更新时由Dep统一通知
    // 
    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 
    	 }, 
     	}); 
    }
    

3 Vue2.0 缺点和解决办法

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。

  1. Vue 无法检测 property 的添加或移除——通过Vue.set(vm.someObject, ‘b’, 2),或者 vm.$set

    由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty 进行劫持。

  2. 无法检测数组更改例如 a[1]=0;——同上方法Vue.set(vm.items, indexOfItem, newValue)

    当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
    当你修改数组的长度时,例如:vm.items.length = newLength

    数组的[‘push’, ‘unshift’, ‘splice’, ‘reverse’, ‘sort’, ‘shift’, ‘pop’]这些方法中splice、‘push’、unshift是劫持不到的,在vue2.0源码中,是对数组的这三个方法进行了重写。具体如下:

    let arrayProto = Array.prototype;
    let newProto = Object.create(arrayProto); 
    const arrMethods=['push', 'unshift', 'splice', 'reverse', 'sort', 'shift', 'pop'];
    arrMethods.forEach(method => {
        newProto[method] = function(...args) {
            let inserted = null;
            switch (method){
                // args 是一个数组
                case 'push':
                    inserted = args;
                    break;
                case 'unshift':
                    inserted = args;
                    break;
                case 'splice': //splice方法有三个参数,第三个参数才是被新增的元素
                    inserted = args.slice(2);//slice返回一个新的数组
                    break;
            }
            if (inserted) ArrayObserver(inserted);      //因为inserted是一个新的数组项,所以要对数组的新增项重新进行劫持
            arrayProto[method].call(this, ...args);     //调用数组本身对应的方法
        }
    })
    function observer(obj){
    ...
        //通过Object.defineProperty进行循环递归绑定
    }
    function ArrayObserver(obj){ //对数组的新增项进行监控
        obj.forEach(item => {
            observer(item);  
        });
    }
    
  3. 不能在data里面增加项——初始时写入data,值为空

  4. 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题。

vue3.0 双向数据绑定原理

Vue3.0基于Proxy来做数据大劫持代理,可以原生支持到数组的响应式,不需要重写数组的原型,还可以直接支持新增和删除属性, 比Vue2.x的Object.defineProperty更加的清晰明了。
Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进行监听操作,完全可以代理所有属性。

const proxyData = new Proxy(data, {
   get(target,key,receive){ 
     // 只处理本身(非原型)的属性
     const ownKeys = Reflect.ownKeys(target)
     if(ownKeys.includes(key)){
       console.log('get',key) // 监听
     }
     const result = Reflect.get(target,key,receive)
     return result
   },
   set(target, key, val, reveive){
     // 重复的数据,不处理
     const oldVal = target[key]
     if(val == oldVal){
       return true
     }
     const result = Reflect.set(target, key, val,reveive)
     return result
   },
   // 删除属性
   deleteProperty(target, key){
     const result = Reflect.deleteProperty(target,key)
     return result
   }
 })

Proxy直接会代理监听data的内容,非常的简单方便,唯一的不足就是部分浏览器无法兼容Proxy,也不能hack,所以目前只能兼容到IE11。
后期会有专门的博文解释说明es6中的Proxy

vue2.0和vue3.0 的差异

Vue2.0

  • 基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式。
  • Object.defineProperty 无法检测到对象属性的添加和删除 。
  • 由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让Vue将它转换为响应式。
  • 深度监听需要一次性递归,对性能影响比较大。

Vue3.0

  • 基于Proxy和Reflect,可以原生监听数组,可以监听对象属性的添加和删除。
  • 不需要一次性遍历data的属性,可以显著提高性能。
  • 因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11 。

Object.defineProperty和Proxy的对比

  • 参数不同

    使用Object.defineProperty对象以及对象属性的劫持+发布订阅模式。
    语法:
    Object.defineproperty( object,‘ propName ’ ,descriptor);
    object:要监听的目标对象
    propName :要定义或修改的属性的名称。
    descriptor:要定义或修改的属性描述符,操作详情。

    let pObj = new Proxy(target, handler);
    target 代表需要添加代理的对象
    handler 用来自定义对象中的操作

  • 返回值不同

    Object.defineProperty返回值
    被传递给函数的对象,就是要定义或修改属性的对象

    Proxy 返回值
    一个Proxy代理的对象,操作这个对象会触发handler对应操作。改变原始对象不会触发。

  • 数据类型不同

    Object.defineProperty是函数
    Proxy是一个对象

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

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

相关文章

Kubectl 的使用——k8s陈述式资源管理

一、kebuctl简介: kubectl 是官方的CLI命令行工具,用于与 apiserver 进行通信,将用户在命令行输入的命令,组织并转化为 apiserver 能识别的信息,进而实现管理 k8s 各种资源的一种有效途径。 对资源的增、删、查操作比较方便&…

欢聚笔试题求助帖

事情是这样的,这段时间一直在求职投简历,期望在暑假之前接到一份大数据开发的实习工作。投了很多公司,然后就收到了欢聚的笔试邀约,HR说要我一天之内做出来,恰巧第二天还有组会要汇报,我就先放下了&#xf…

21.1zabbix低级自动发现-监控项详解

详解分析:低级自动发现:自动创建监控项(红色部分字体是怎么创建得监控项?) 点击对应得主机-监控项-Network interfaces应用集,键值有进4个,出4个。因为本机存在4块网卡 注释:本机存…

BGP(一)边界网关协议

BGP协议基础 路由分类 直连路由 非直连路由(间接路由) 静态路由动态路由 IGP:内网网关路由协议(在企业内部或数据中心内部使用) DV:距离矢量路由协议RIP(v1/v2)IGRP——网络直径&…

文本转语音软件-TTSMaker

一、TTSMaker介绍 TTSMaker(马克配音)是一款免费的文本转语音工具,提供语音合成服务,支持多种语言,包括中文、英语、日语、韩语、法语、德语、西班牙语、阿拉伯语等50多种语言,以及超过300种语音风格。 可…

Leetcode刷题笔记2:数组基础2

导语 leetcode刷题笔记记录,本篇博客记录数组基础1部分的题目,主要题目包括: 977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II 知识点 滑动窗口 所谓滑动窗口,就是不断的调节子序列的起始位…

(二)vForm 动态表单设计器之下拉、选择

系列文章目录 (一)vForm 动态表单设计器之使用 目录 系列文章目录 前言 一、后端需提供接口 二、组件配置 总结 前言 动态表单下拉、选择等组件,大概率要使用数据库中的数据,那么vForm如何拿到数据库中的数据呢?跟随…

Xed编辑器开发第二期:使用Rust从0到1写一个文本编辑器

第三篇 这部分接着处理用户退出命令以及一些其他新功能; 3.1 使用CtrlQ退出 modifiers: event::KeyModifiers::CONTROL,使用CONTROL替换之前的NONE值即可; 3.2 重构键盘输入 让我们重构我们的代码,以便我们有一个用于低级按键读取的函数&…

php部分特性漏洞学习

php部分函数漏洞学习 简单总结一些我遇到的ctf中的php的一些函数或特性的漏洞,我刷题还是太少了,所以很多例子来自ctfshow,以后遇到相关赛题再更新 1.MD5和其他hash 弱类型比较 php中,有两中判断相等的符号,和&…

flutter使用dbus插件时,在终端无法使用“dart-dbus”命令

不用flutter的人,可能都不会找到这儿,遇到这个问题,所以这里默认flutter已经装过了,且对flutter如何使用插件也有所了解了。 由于我在项目中用到了dbus插件,用法如图所示,我需要使用这条命令来生成一个sou…

前端javascript包管理,npm升级用pnpm

一 pnpm 介绍 pnpm(Package Manager)是一个快速、节省磁盘空间的 JavaScript 包管理器,它是 Node.js 生态系统中 npm 的一个替代品。pnpm 解决了传统包管理工具在处理依赖时的一些痛点,特别是关于存储空间使用和依赖地狱的问题。…

vite vue-cli 之vue3安装Vue devtools调试工具

一.vite的官方配置-vite-plugin-vue-devtools vite.config.js import { fileURLToPath, URL } from node:urlimport vue from vitejs/plugin-vue import vueJsx from vitejs/plugin-vue-jsx import { defineConfig } from vite import VueDevTools from vite-plugin-vue-devt…

Android JetPack ViewModel

一、什么是ViewModel? Android ViewModel在我们使用MVVM开发模式时会经常用到,对我来说就是好用,好维护。 它相对于MVC模式, 一来可以减少Activity层的代码,可以把一些业务逻辑和对数据的交互到ViewModel层去&#…

Excel模板计算得出表格看板

背景 表格看板及导出,单元格时间年是根据筛选器时间变化的 较往年和往年是计算单元格 思路 1.通过excel模板来把数据填入excel再数据清洗得到数据返回前端 2.数据填充,通过行列作为key 列如:key整体20241月,根据key匹配数据填…

计算机操作系统总结(1)

1操作系统的概念(定义)功能和目标 (1)什么是操作系统? (2)操作系统的功能和目标—作为系统资源的管理者 (3)操作系统的功能和目标—向上层提供方便易用的服务 (4)操作系…

中银基金软件开发工程师春招群面记录

本文介绍2024届春招中,中国银行下属中银基金管理有限公司的软件开发工程师岗位1场面试的基本情况、提问问题等。 2024年04月投递了中国银行的共计4个部门或单位,包括中银基金管理有限公司的软件开发工程师岗位,暂时不清楚所在部门。目前完成了…

平均分配问题

将dwd_phone_shop这六行数据按照1 3 5,2 4 6的排序分为两组,与dwd_phone_base连接,分别与为其中1 3 5 为shop_Id为1的那一组 2 4 6 为shop_Id为2 的那一组 取余法 开窗函数法

南加州大学字节提出MagicPose,提供逼真的人类视频生成,实现生动的运动和面部表情传输,以及不需要任何微调的一致的野外零镜头生成。

MagicPose可以精确地生成外观一致的结果,而原始的文本到图像模型(如Stable Diffusion和ControlNet)很难准确地保持主体身份信息。 此外,MagicPose模块可以被视为原始文本到图像模型的扩展/插件,而无需修改其预训练的权重。 相关链接 论文链…

javaSwing飞机订票系统

摘要 Java swing实现的飞机票预定系统,系统数据库原本采用的是Oracle,我又改了一个mysql版本的,所以这套系统有两个版本,一个是mysql数据库版的,一个是Oracle数据库版 一. 已经完成的功能 : …

Linux基础命令[27]-gpasswd

文章目录 1. gpasswd 命令说明2. gpasswd 命令语法3. gpasswd 命令示例3.1 不加参数3.2 -a(将用户加入组)3.3 -d(从组中删除用户)3.4 -r(删除组密码)3.5 -M(多个用户一起加入组)3.6 …