看完就懂的vue2与vue3响应式的区别

news2024/12/24 3:17:02

一、前言

数据响应式
所谓数据响应式就是建立响应式数据与依赖(调用了响应式数据的操作)之间的关系,当响应式数据发生变化时,可以通知那些使用了这些响应式数据的依赖操作进行相关更新操作,可以是DOM更新,也可以是执行一些回调函数。

从Vue2到Vue3都使用了响应式,那么它们之间有什么区别?

  • Vue2响应式:基于Object.defineProperty()实现的。
  • Vue3响应式:基于Proxy实现的。

那么它们之间有什么区别?为什么Vue3会选择Proxy替代defineProperty?

请听我娓娓道来~~
在这里插入图片描述

二、Object.defineProperty()

1、Object.defineProperty

Object.defineProperty(obj, prop, descriptor) 方法会直接在一个对象上定义一个新属性,或修改一个对象的现有属性,并返回此对象,其参数具体为:

obj:要定义属性的对象

prop:要定义或修改的 属性名称 或 Symbol

descriptor:要定义或修改的 属性描述符

从以上的描述就可以看出一些限制,比如: 目标是 对象属性,不是 整个对象 一次只能 定义或修改一个属性
当然有对应的一次处理多个属性的方法Object.defineProperties(),但在 vue 中并不适用,因为 vue不能提前知道用户传入的对象都有什么属性,因此还是得经过类似 Object.keys() + for 循环的方式获取所有的 key ->value,而这其实是没有必要使用 Object.defineProperties()

2、为什么使用Object.defineProperty

const obj = {
    a: 1,
    b: 2,
    c: {
        a: 1,
        b: 2
    }
}
// obj.a
// obj.a = 3

首先思考:vue的响应式到底要干什么?无非就是做一件事,就是当我们读这个对象属性的时候,我们要知道它读了,我要做些别的事我要插一脚。当给它重新赋值的时候,我要知道它在重新赋值,我要插一脚。

上面代码现在这脚就插不进去,所以要想一个办法,把这个属性的读取和赋值变成一个函数,希望将来读这个属性的时候,运行这么个函数,给这个属性赋值的时候,把新的值传给我。一变函数就简单了,不要说插一脚,100脚都不是问题。

那怎么变成函数呢,在ES6之前,没有别的办法。只有Object.defineProperty()

const obj = {
    a: 1,
    b: 2,
    c: {
        a: 1,
        b: 2
    }
}

let v = obj.a // 拿到原始值
Object.defineProperty(obj, 'a', {
    get () { // 读的时候 运行get
        console.log('a', '读取')
        return v
    },
    set (val) { // 赋值的时候,运行set
        if (val !== v) {
            console.log('a', '更改')
            v = val
        }
    }
})
obj.a
obj.a = 3

由于vue2是针对属性的监听,所以就必须要去深度遍历每一个属性,所以在vue2里面就有一个observe(),要玩这个对象之前,先对它进行监听。

const obj = {
    a: 1,
    b: 2,
    c: {
        a: 1,
        b: 2
    }
}
// 判断是不是Object
function isObject(v) {
	return typeof v === 'object' && v !==  null
}
// 观察 在这一步完成监听
function observe(obj) {
	for (const k in obj) {
		let v = obj[k]
		if (isObject(v)) { // 如果属性值仍然是个对象,深度遍历
			observe(v)
		}
		Object.defineProperty(obj, 'k', {
		    get () { // 读的时候 运行get
		        console.log('k', '读取')
		        return v
		    },
		    set (val) { // 赋值的时候,运行set
		        if (val !== v) {
		            console.log('k', '更改')
		            v = val
		        }
		    }
		})
	}
}


obj.a
obj.a = 3
obj.bbbbb = 666 // 没有被监听到
delete obj.a // 没有被监听到

在vue2里面观察的方式就是深度遍历每一个属性,把每一个属性的读取和赋值变成函数,只要变成函数,就可以插一脚。具体这一脚是做啥,再次先不讨论哈。

这就是vue2的做法,但是有一个天生的缺陷

由于它是针对每个属性的监听,所以他就必须要进行深度的遍历,这会有效率的损失。由于在观察这个步骤里面,它完成了深度遍历,在观察这个步骤时间点,有的属性都被监听到了,都被改成了get和set函数了,观察这一步做完后,再去新增属性,就不知道了,对这个属性而言,是没有被监听的。

我们学vue生命周期,在created()之前就完成了监听。后修再添加属性,它就不知道了,这就是为什么vue2无法监听属性的新增、删除。

我们再整理下:

defineReactive(data,key,val){
    Object.defineProperty(data,key,{
      enumerable:true,
      configurable:true,
      get:function(){
        console.log(`对象属性:${key}访问defineReactive的get!`)
        return val;
      },
      set:function(newVal){
        if(val===newVal){
          return;
        }
        val = newVal;
        console.log(`对象属性:${key}访问defineReactive的get!`)
      }
    })
}
let obj = {};
this.defineReactive(obj,'name','sapper');
// 修改obj的name属性
obj.name = '工兵';
console.log('obj',obj.name);
// 为obj添加age属性
obj.age = 12;
console.log('obj',obj);
console.log('obj.age',obj.age);
// 为obj添加数组属性
obj.hobby = ['游戏', '原神'];
obj.hobby[0] = '王者';
console.log('obj.hobby',obj.hobby);

// 为obj添加对象属性
obj.student = {school:'大学'};
obj.student.school = '学院';
console.log('obj.student.school',obj.student.school);

在这里插入图片描述
从上图可以看出使用defineProperty定义了包含name属性的对象obj,然后添加age属性、添加hobby属性(数组)、添加student属性并分别访问,都没有触发obj对象中的get、set方法。

也就是说defineProperty定义对象不能监听添加额外属性或修改额外添加的属性的变化,我们再看看这样一个例子:

let obj = {};
// 初始化就添加hobby
this.defineReactive(obj,'hobby',['游戏', '原神']);
// 改变数组下标0的值
obj.hobby[0] = '王者';
console.log('obj.hobby',obj.hobby);

在这里插入图片描述

假如我们一开始就为obj添加hobby属性,我们发现修改数组下标0的值,并没有触发obj里的set方法

也就是说defineProperty定义对象不能监听根据自身数组下标修改数组元素的变化。

Object.defineProperty():

  • defineProperty定义对象不能监听添加额外属性或修改额外添加的属性的变化
  • defineProperty定义对象不能监听根据自身数组下标修改数组元素的变化

3、Object.defineProperty 拦截 Array

Object.defineProperty 可用于实现对象属性的 get 和 set 拦截,而数组其实也是对象,那自然是可以实现对应的拦截操作,如下:
在这里插入图片描述
Vue2 为什么不使用 Object.defineProperty 拦截 Array?
尤大在曾在 GitHub 的 Issue 中做回复: 我是因为性能问题

  • 数组 和 普通对象 在使用场景下有区别,在项目中使用数组的目的大多是为了 遍历,即比较少会使用 array[index] = xxx 的形式,更多的是使用数组的 Api 的方式
  • 数组长度是多变的,不可能像普通对象一样先在 data 选项中提前声明好所有元素,比如通过 array[index] = xxx 方式赋值时,一旦 index 的值超过了现有的最大索引值,那么当前的添加的新元素也不会具有响应式
  • 数组存储的元素比较多,不可能为每个数组元素都设置 getter/setter
  • 法拦截数组原生方法如 push、pop、shift、unshift 等的调用,最终仍需 重写/增强 原生方法

4、缺陷

在这里插入图片描述

三、Proxy

无论是vue2还是vue3,都必须把读取和赋值变成函数,这是必须的,不玩玩不了。
只不过变函数的方式不一样,在vue3里面就不会针对这些属性进行监听了,而是直接监听整个对象。那这就简单了,都不需要遍历了。只要在动这个对象就能收到通知,那么是怎么做到的呢?就是proxy。

这样不管是读的哪一个属性,给属性重新赋值的时候,也会收到通知。这样就会产生一个代理对象。使用这个属性都是通过这个代理对象去做的。

const obj = {
    a: 1,
    b: 2,
    c: {
        a: 1,
        b: 2
    }
}
// 观察
new Proxy(obj, {
	get (target, k) { // 读的时候 运行get
		let v = target[k]
        console.log('k', '读取')
        return v
    },
    set (target, k, val) { // 赋值的时候,运行set
        if (target[k] !== val) {
            console.log('k', '更改')
            target[k] = val
        }
    },
    deleteProperty(){ // 删除属性监听
	
	}
})

proxy.a = 3
proxy.b
proxy.ccccccccc

由于它不去监听属性了,就不需要遍历了,监听的是整个对象,所以之后对属性的操作,都是能收到通知的。
虽然这个代码和vue的源码还有很多细节上的差别,但是核心道理就是如此。

我们再整理下:

const obj = {
    a: 1,
    b: 2,
    c: {
        a: 1,
        b: 2
    }
}

// 判断是不是Object
function isObject(v) {
	return typeof v === 'object' && v !==  null
}

// 观察
function observe(obj){
	const proxy = new Proxy(obj, {
		get (target, k) { // 读的时候 运行get
			console.log('k', '读取')
			let v = target[k]
	        if (isObject(v)) { // 虽然是递归,但是不会影响一开始的效率
	        	v = observe(v)
	        }
	        return v
	    },
	    set (target, k, val) { // 赋值的时候,运行set
	        if (target[k] !== val) {
	            console.log('k', '更改')
	            target[k] = val
	        }
	    },
	    deleteProperty(){ // 删除属性监听
		
		}
	})
	return proxy 
}
const proxy = observe(obj)

proxy.a = 3
proxy.b
proxy.ccccccccc

再看看例子:

// proxy实现
let targetProxy = {name:'sapper'};
let objProxy = new Proxy(targetProxy,{
    get(target,key){
      console.log(`对象属性:${key}访问Proxy的get!`)
      return target[key];
    },
    set(target,key,newVal){
      if(target[key]===newVal){
        return;
      }
      console.log(`对象属性:${key}访问Proxy的set!`)
      target[key]=newVal;
      return target[key];
    }
})
// 修改objProxy的name属性
objProxy.name = '工兵';
console.log('objProxy.name',objProxy.name);
// 为objProxy添加age属性
objProxy.age = 12;
console.log('objProxy.age',objProxy.age);
// 为objProxy添加hobby属性
objProxy.hobby = ['游戏', '原神'];
objProxy.hobby[0] = '王者';
console.log('objProxy.hobby',objProxy.hobby);
// 为objProxy添加对象属性
objProxy.student = {school:'大学'};
objProxy.student.school = '学院';
console.log('objProxy.student.school',objProxy.student.school);

在这里插入图片描述
从上图是不是发现了Proxy与defineProperty的明显区别之处了,Proxy能支持对象添加或修改触发get、set方法,不管对象内部有什么属性。

我们再看看Vue里的用法例子:

 data() {
   return {
     name: 'sapper',
     student: {
       name: 'sapper',
       hobby: ['原神', '天涯明月刀'],
     },
   };
 },
 methods: {
   deleteName() {
     delete this.student.name;
     console.log('删除了name', this.student);
   },
   addItem() {
     this.student.age = 21;
     console.log('添加了this.student的属性', this.student);
   },
   updateArr() {
     this.student.hobby[0] = '王者';
     console.log('更新了this.student的hobby', this.student);
   },
}

在这里插入图片描述
从图中确实可以修改data里的属性,但是不能及时渲染,所以Vue2提供了两个属性方法解决了这个问题:Vue. s e t 和 V u e . set和Vue. setVue.delete。注意不能直接this._ data.age这样去添加age属性,也是不支持的。

this.$delete(this.student, 'name');// 删除student对象属性name
this.$set(this.student, 'age', '21');// 添加student对象属性age
this.$set(this.student.hobby, 0, '王者');// 更新student对象属性hobby数组

在这里插入图片描述

const user = {name:'张三'}
const obj = new Proxy(user,{
  get:function (target,key){
    console.log("get run");
    return target[key];
  },
  set:function (target,key,val){
    console.log("set run");
    target[key]=val;
    return true;
  }
})
obj.age = 22;
console.log(obj); // 监听对象添加额外属性打印set run!  
const obj = new Proxy([2,1],{
  get:function (target,key){
    console.log("get run");
    return target[key];
  },
  set:function (target,key,val){
    console.log("set run");
    target[key]=val;
    return true;
  }
})
obj[0] = 3;
console.log(obj); // 监听到了数组元素的变化打印set run!  
  • Proxy:解决了上面两个弊端,proxy可以实现:
  • 可以直接监听对象而非对象属性,可以监听对象添加额外属性的变化;
  • 可以直接监听数组的变化
  • Proxy 返回的是一个新对象,而 Object.defineProperty 只能遍历对象属性直接修改。
  • 支持多达13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是Object.defineProperty 不具备的。

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

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

相关文章

视频音频提取器推荐:快速提取视频中的音频!

视频中的音频可以用于很多用途,比如制作配乐、音频剪辑等。但是,许多人并不知道如何将视频中的音频提取出来。如果您也是这样的情况,那么本文为您介绍一个简单易用的视频音频提取器:。 它是一个免费的在线工具,可以帮…

【Android FrameWork(一)】- 启动程序 init

文章目录 背景源码分析(第一个启动程序 init)1.main.cpp2.init.cpp3.property_service.cpp4,LoadBootScripts 拓展知识Android的架构图I/O多路复用 总结 离职找工作间隙,停下脚步整理下自己的知识体系,把之前忙于开发未曾整理的知…

SOLIDWORKS Electrical无缝集成电气和机械设计

集成电气系统设计SOLIDWORKS⑧Electrical 解决方案借助专为工程专业设计的特定工具简化了电气铲品设计,并借助直观的用户界面更快地设计嵌入式电气系统。 与SOLIDWORKS 3DCAD的原生集成能提供更好的协作与生产效率,同时减少产品延迟、提高设计的一致性与…

微信小程序nodejs+python+php+springboot+vue 法律知识分享科普系统平台

要想实现法律知识分享平台的各项功能,需要后台数据库的大力支持。管理员验证注册信息,收集的信息,并由此分析得出的关联信息等大量的数据都由数据库管理。本文中数据库服务器端采用了Mysql作为后台数据库 紧密联系起来。在设计过程中&#xf…

从零开始,轻松入门React - 构建现代Web应用的利器!

文章目录 前言渲染 React 组件使用 JSX传递属性(Props)处理组件状态(State)处理用户输入(事件处理)组合和嵌套组件写在最后 前言 React 是一种由 Facebook 开发的流行的 JavaScript 库,用于构建…

通用人工智能+智能车舱,商汤绝影带来怎样一番景象?

本文来源:智车科技 / 导读 / 进入4月,中国科技圈迎来最卷的时刻,这可能中国AI史上一个重要节点。ChatGPT、GPT-4的出圈掀起了通用人工智能(AGI)的技术狂潮,引爆了一场以大模型为代表的生成式AI狂飙&#x…

【基础】Kafka -- 日志存储

Kafka -- 日志存储 日志文件目录日志索引偏移量索引时间戳索引 日志清理日志删除基于时间基于日志大小基于日志起始偏移量 日志压缩 日志文件目录 Kafka 中的消息以主题为单位进行基本归类,而每个主题又可以划分为一个或者多个分区。在不考虑多副本的情况下&#x…

Adobe Photoshop 软件下载

Adobe Photoshop,简称“PS”,是由Adobe Systems开发和发行的图像处理软件。Photoshop主要处理以像素所构成的数字图像。 时至今日,Adobe Photoshop 已经成为当今世界上最流行、应用最广泛的图像处理软件。不但设计专业的学生要系统的学习这个…

【算法】最容易懂得的红黑树

红黑树是一个平衡的二叉树,但不是一个完美的平衡二叉树。虽然我们希望一个所有查找都能在~lgN次比较内结束,但是这样在动态插入中保持树的完美平衡代价太高,所以,我们稍微放松逛一下限制,希望找到一个能在对数时间内完…

【iOS】AVPlayer 视频播放

视频播放器的类别 iOS开发中不可避免地会遇到音视频播放方面的需求。 常用的音频播放器有 AVAudioPlayer、AVPlayer 等。不同的是,AVAudioPlayer 只支持本地音频的播放,而 AVPlayer 既支持本地音频播放,也支持网络音频播放。 常用的视频播放…

python学习——【第八弹】

前言 上篇文章 python学习——【第七弹】学习了python中的可变序列集合,自此python中的序列的学习就完成啦,这篇文章开始学习python中的函数。 函数 在学习其他编程语言的时候我们就了解过函数:函数就是执行特定任何以完成特定功能的一段代…

【JWT】token jwt-跨域认证的问题、JWT 的原理、java JWT实用案例

本文是向大家介绍token JWT的相关学习,它能够实现登录认证功能的实现,了解它能够让我们对系统有更加全面系统的理解 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,本文介绍它的原理和用法。 一、跨域认证的问题 互联…

动态组件、keep-alive的使用及自定义指令

目录 1. 动态组件 2.如何实现动态组件渲染 3. 使用keep-alive保持状态 4. keep-alive对应的生命周期函数 5. keep-alive的include属性 自定义指令 1.什么是自定义指令 2. 自定义指令的分类 3. 私有自定义指令 4. update函数 5. 函数简写 全局自定义指令: …

媒体沟通会 | 云擎未来 智信天下:移动云大会终极预告

4月24日,“云擎未来 智信天下”2023移动云大会媒体沟通会在苏州举办,百余家主流新闻媒体参会。在媒体沟通会现场,中国移动云能力中心副总经理吴世俊致欢迎词,市场部副总经理吴炯详细介绍了移动云大会盛况,并透露在本届…

三问 ThreadLocal —— 有什么用 ? 使用时有什么潜在风险?原理 ?

最近想实现一些功能,求诸于网络之后,得到了使用 ThreadLocal 实现的方式,那么 ThreadLocal 到底是什么呢 ? 遂写此文,抽丝剥茧的来看一下这个 Java 并发类 。 最近,me 的 gpt 账号没了,所以不能…

【模板】Hexo Docker Nginx 个人博客服务器部署

上文:基于 Hexo 的 Github 博客搭建 注意:通过验证部署,确定无误。AI生成的部分有🤖图标。 🤖 TLDR By ChatGPT 本指南提供了在服务器上设置Git仓库、将本地Hexo页面推送到服务器仓库、在服务器上创建Nginx配置文件以…

数字信封例程不支持的bug,以及卸载安装配置Node.js

文章目录 前言一、运行错误:0308010C:数字信封例程:不支持二、卸载Node.js三、重新安装Node.js总结 前言 下载了若依项目,但是在前端项目运行打包都出现了bug。最后,卸载了Node.js,并重新安装了低版本的Node.js。 一、运行错误:0308010C:数字…

[算法前沿]--003-AGI通用人工智能模型对安全的影响和开源的大模型

文章目录 0.ChatGPT大模型带来的影响0.1 ChatGPT带来信息化革命性创新,目前尚不能处理专业知识但成长很快0.2 Chat GPT为网安行业带来新的创新方向,也将引领新一轮投融资热潮0.2.1 攻击方发起网络攻击的门槛降低0.2.2 防守方合理使用ChatGPT可大幅减少安…

组态王与PLC之间1主多从自组网无线通信

本方案是基于三菱专用协议下实现的1主多从自组网无线通信形式,主站为组态王,从站为三菱FX3U PLC和485BD扩展。采用日系PLC专用无线通讯终端DTD435MC-V96,作为实现无线通讯的硬件设备,来解决组态王与PLC之间的通讯问题。 一、方案…

百度AI模型“文心一言”新鲜体验

今天收到通知可以体验百度的AI模型“文心一言”,等了一个多月迫不及待的去体验了一把,以下是体验的相关记录。 1、简单介绍 通过文心一言官网链接https://yiyan.baidu.com/进入,看到如下界面: 在文心一言的自我介绍中&#xff0c…