从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷

这篇具有很好参考价值的文章主要介绍了从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章目录

内容 链接
从Vue2到Vue3【零】 Vue3简介
从Vue2到Vue3【一】 Composition API(第一章)
从Vue2到Vue3【二】 Composition API(第二章)
从Vue2到Vue3【三】 Composition API(第三章)
从Vue2到Vue3【四】 Composition API(第四章)
从Vue2到Vue3【五】 新的组件(Fragment、Teleport、Suspense)
从Vue2到Vue3【六】 Vue3的改变
从Vue2到Vue3【七】 Vue2中响应式原理的实现及其缺陷


前言

Vue是一款流行的JavaScript框架,它以其简洁易用的语法和强大的响应式能力而受到广泛关注。Vue的响应式数据机制是其核心特性之一,它能够自动追踪数据的变化,并实时更新相关的视图。然而,Vue 2中的响应式数据机制并非完美无缺,本文将探讨Vue 2响应式原理及其存在的缺陷。


一、Vue2中的响应式原理

  • Vue 2的响应式原理:
    在Vue 2中,响应式是通过使用Object.defineProperty()方法来实现的
    在组件实例化过程中,Vue会对数据对象(data)进行递归地遍历,将每个属性都转换为getter/setter,并且为每个属性创建一个依赖追踪的系统。当属性被访问或修改时,getter/setter会触发依赖追踪系统,从而进行依赖收集派发更新,以保证数据和视图的同步。
  • 具体实现步骤如下:
    1.创建Observer对象:通过递归地将data对象的属性转换为响应式属性,使用Object.defineProperty()为每个属性添加getter和setter方法Vue2中 通过使用 Object.defineProperty() 方法,将对象的属性转换成 getter 和 setter,当数据发生变化时,会自动触发相应的更新函数,实现数据的响应式。
    2.创建Dep对象:用来管理 Watcher,它用来收集依赖、删除依赖和向依赖发送消息等。用于解耦属性的依赖收集和派发更新操作。
    3.创建Watcher对象:Watcher对象用于连接视图和数据之间的桥梁,当被依赖的属性发生变化时Watcher对象会接收到通知并更新视图。当数据发生变化时,它会通知订阅该数据的组件更新视图。Watcher 在实例化时会将自己添加到 Dep 中,当数据发生变化时,会触发相应的更新函数。
    4.模板编译:Vue会解析模板,将模板中的数据绑定指令转译为对应的更新函数,以便在数据发生变化时调用。

在修改对象的值的时候,会触发对应的 setter, setter通知之前依赖收集得到的 Dep 中的Watcher,告诉它自己的值改变了,需要重新渲染视图。这时候这些 Watcher就会开始调用 update 来更新视图, 对应的getter触发追踪,把新值重新渲染到视图上

input用v-model绑定数据,我们需要在input元素上添加事件监听,每当input事件被触发时,就修改对应的data,data里的数据又会响应式更新回视图

二、模拟简易版响应式原理

实现思路:
定义一个Observe构造函数用于对data对象的属性进行数据劫持。我们使用Object.defineProperty()方法对data对象的每个属性进行劫持,定义了属性的getter和setter方法。
在getter方法中,我们返回属性的值。在setter方法中,我们判断新值是否与旧值不同,如果不同,则更新属性的值,并触发依赖更新

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>Document</title>
	</head>
	<body>
		<script type="text/javascript" >

			let data = {
				name:'前端百草阁',
				age:21,
			}

			function Observer(obj){
				//汇总对象中所有的键形成一个数组
				const keys = Object.keys(obj)
				//遍历
				keys.forEach((k)=>{
					Object.defineProperty(this,k,{
						get(){
							return obj[k]
						},
						set(val){
							console.log(`${k}被改了,我要通知Vue重新去解析模板.....`)
							obj[k] = val
						}
					})
				})
			}
			
			//创建一个监视的实例对象,用于监视data中属性的变化
			const obs = new Observer(data)		
	
			//准备一个vm实例对象
			let vm = {}
			vm._data = data = obs
		</script>
	</body>
</html>

从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
这个时候,原先data里的属性就会各自有一个为他们服务的getter和setter,变成了具有响应式的属性

  • 简易式版本的缺陷
    • 缺陷一:正常vue中会做一个数据代理,当访问vm.name时,访问的其实是vm._data.name,这样做了数据代理后使用起来更方便
      从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
    • 缺陷二: 简易式版本没有考虑到data里面的属性值还是对象的情况,在Vue中利用递归的方法将data里所有的属性通过递归的方式都转换为了响应式属性(即使属性值是一个数组,数组里藏了对象,依然可以把对应的属性转换为响应式属性)

这里有一个小tips,利用this指向obs,访问this(obs)里的属性,getter返回的其实是obj里的属性(数据代理),为什么要这样呢?如果说你访问obj里的属性,我真的通过getter给你返回了obj里对应的属性,返回的obj里的属性又要去触发自己的getter,那是不是就陷入死循环了呢?导致的问题就是无论你是触发getter 还是setter都会导致超出最大调用堆栈这个错误

从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
解决这个问题还有一个办法就是利用闭包,利用闭包把初始值传给value存起来了,后续getter和setter都是针对闭包内的value,和原本的obj隔离开了,当你访问或者设置obj.key的时候,就会去修改对应的val(由于闭包val不会被垃圾机制回收)就不存在最大调用堆栈溢出的情况了

function observe(obj) {
  if (!obj || typeof obj !== 'object') {
    return;
  }

  Object.keys(obj).forEach(function(key) {
    defineReactive(obj, key, obj[key]); 
  });
}

function defineReactive(obj, key, val) {
  observe(val);  // 递归地对data对象的属性进行数据劫持

  Object.defineProperty(obj, key, {
    get: function() {
      return val;
    },
    set: function(newValue) {
      if (newValue !== val) {
        val = newValue;
        // 触发依赖更新
        updateView();
      }
    }
  });
}

function updateView() {
  document.querySelector('h1').innerText = vm.message;
}

// 初始化数据劫持
observe(vm.$data);

在上述代码中,observe函数用于递归地对data对象的属性进行数据劫持。在defineReactive函数中,我们使用Object.defineProperty()方法对data对象的每个属性进行劫持,定义了属性的getter和setter方法。
在getter方法中,我们返回属性的值。在setter方法中,我们判断新值是否与旧值不同,如果不同,则更新属性的值,并触发依赖更新。
最后,我们调用observe(vm.$data)来初始化数据劫持,使得Vue能够捕获到对data对象属性的访问和修改操作,并触发相应的依赖更新。

三、Vue2响应式数据带来的缺陷

Vue 2中的响应式数据存在一些缺陷,但通过使用Vue提供的补救办法,可以解决大部分响应式数据的问题。

3.1 新增属性的响应问题

Vue在初始化时会对data对象的属性进行数据劫持,但是对于后续新增的属性,Vue无法自动进行响应式处理。
Vue 无法探测普通的新增属性 ,比如 this.myObject.saying = 'hi'这个新增的saying属性是不具有响应式的,Vue探测不到

3.2 数组变动的响应问题

Vue对数组的变动(例如通过索引修改数组元素、通过splice方法删除或插入元素)无法直接进行响应式处理。

例如此时在data里定义了这些数据

	data:{
		friends:[
			{name:'jerry',age:35},
			{name:'tony',age:36},
			'前端百草阁'
		]
	}

从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
不难发现数组中的对象都是响应式的,但数组中的普通元素却不是响应式的,意味着若直接修改数组中的元素Vue无法监测到

如果你通过数组下标修改对象属性的话是可以监测的,因为对象里的属性都是响应式的,但如果你通过数组下标修改普通元素是无法监测到的

如果用一个新数组覆盖掉原先的数组,Vue是能监测到的从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端

3.3 对象属性的删除问题

Vue无法直接检测到对象属性的删除操作。
利用delete删除对象的属性,无法被Vue监测到

四、Vue2响应式缺陷的解决办法

4.1 新增属性的响应问题

Vue.set( target, propertyName/index, value )

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = ‘hi’)

给data中的student对象添加一个属性,并且是响应式的,有两种写法,Vue.set或者this.$set

// Vue.set(this._data.student,'sex','男') // 这里加不加_data实际上都可以,就是一个数据代理,访问谁都一样,那我们肯定选择偷懒啦
this.$set(this.student,'sex','男')  // this代表vm vue实例对象

从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
实现了新增了student对象里的sex属性,并且该属性有为自己服务的getter、setter(具有响应式)

但是,Vue官网明确指出:注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
简单来说就是,set方法的第一个参数target不允许 是vm(vue实例)、也不允许是vm._data(根数据对象)

4.2 数组变动的响应问题

第一中解决办法,使用数组变异方法:Vue提供了一些数组变异方法(例如push、pop、shift、unshift、splice、sort和reverse),这些方法会触发数组的响应式更新。
如果不是这七个方法的话,比如调用slice等数组方法的话,记得要把返回的新数组覆盖掉原来的旧数组,依然能触发响应式
从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
第二种解决办法,利用set方法,set方法不但能解决对象新增属性的问题,还能解决修改数组的问题(用的不多)
从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端

4.3 对象属性的删除问题

Vue.delete方法:用来删除对象的属性,并触发响应式更新。例如,可以使用Vue.delete(vm.someObject, ‘propertyToDelete’)来删除一个属性。
正常的delete方法,虽然确实删除了属性,但是无法被监测到
从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端
利用Vue.delete完美解决删除对象属性无法被监测的问题(很少用到),或者vm.$delete(vm.person,'name')
从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷,从Vue2到Vue3,vue.js,javascript,前端


总结

Vue 2的响应式数据机制在大多数情况下能够满足我们的需求,但也存在一些缺陷。
首先,Vue无法直接响应新增的属性,需要使用特定的方法进行补救。其次,对于数组的变动和对象属性的删除,Vue也无法直接进行响应式处理,需要使用相应的方法来触发更新。这些缺陷在实际开发中可能会带来一些困扰。
但幸运的是,Vue提供了一些补救的办法,如Vue.set和Vue.delete方法,以及数组变异方法。通过这些补救措施,我们可以弥补Vue 2响应式数据机制的不足,提升开发效率和用户体验。尽管如此,我们也期待Vue未来版本的改进,在响应式数据方面能够更加智能和灵活,以满足更多复杂场景的需求。文章来源地址https://www.toymoban.com/news/detail-618826.html

到了这里,关于从Vue2到Vue3【七】——Vue2中响应式原理的实现及其缺陷的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Vue2.0 的响应式原理 私

    使用的Object.defineProperty()重新定义对象,给data的每个属性都添加了getter和setter方法。这时候会为对象的每个属性创建一个Dep实例  (依赖)。Dep实例可以订阅和通知相关的Watcher实例。,  这一步叫  数据劫持  或者 依赖收集 在数据发生更新后调用 set 时会通知发布者 notify

    2024年02月11日
    浏览(26)
  • vue2响应式原理----发布订阅模式

    很多人感觉vue2的响应式其实用到了观察者+发布订阅。我们先来看一下简单的发布订阅的代码: 从上面中发现一个重要的点,发布者和订阅者是根据key值来区分的,然后通过消息中心来中转的,他们家是是实现不知道对方是谁。 而观察者模式中观察者是一开始就知道自己观察

    2024年04月14日
    浏览(28)
  • 202 vue2的响应式原理 通俗易懂!

    Object.defineProperty + 依赖追踪 。 在Vue实例化过程中,会 递归 地将 每个数据对象 的 属性 转换为 getter/setter ,并维护一个 依赖收集器(Dep) 。 每个属性 都有一个关联的 watcher ,当 数据发生 变化时 , watcher 会 被通知 并 更新视图 。 Vue 2.x 实现响应式数据: vue实例化 时,会

    2024年02月06日
    浏览(26)
  • 前端(四)——vue.js、vue、vue2、vue3

    😊博主:小猫娃来啦 😊文章核心: vue.js、vue、vue2、vue3从全局到局部 Vue.js是一款流行的JavaScript框架 vue,vue2,vue3都是vue.js的不同版本。 Vue:Vue.js的第一个版本,也称为Vue 1.x。它于2014年首次发布,并获得了广泛的应用和认可。 Vue2:Vue.js的第二个版本,也称为Vue 2.x。它在Vu

    2024年02月12日
    浏览(46)
  • vue3响应式原理

    Vue 3 中的响应式原理是通过使用 ES6 的 Proxy 对象来实现的。在 Vue 3 中,每个组件都有一个响应式代理对象,当组件中的数据发生变化时,代理对象会立即响应并更新视图。 具体来说,当一个组件被创建时,Vue 会为组件的 data 对象创建一个响应式代理对象。这个代理对象可以

    2024年02月15日
    浏览(58)
  • 【vue3源码系列#01】vue3响应式原理(Proxy)

    专栏分享:vue2源码专栏,vue3源码专栏,vue router源码专栏,玩具项目专栏,硬核💪推荐🙌 欢迎各位ITer关注点赞收藏🌸🌸🌸 在学习 Vue3 是如何进行对象的响应式代理之前,我想我们应该先去了解下 ES6 新增的API Proxy 与 Reflect ,可参考【Vue3响应式入门#02】Proxy and Reflect 。之

    2024年02月05日
    浏览(30)
  • Vue3响应式原理 私

    响应式的本质:当数据变化后会自动执行某个函数映射到组件,自动触发组件的重新渲染。 响应式的实现方式就是劫持数据,Vue3的reactive就是通过Proxy劫持数据,由于劫持的是整个对象,所以可以检测到任何对象的修改,弥补了2.0的不足。 名词解释: **副作用函数:**函数的

    2024年02月10日
    浏览(29)
  • Vue3 数据响应式原理

    核心: 通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等… 通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作 Vue3的响应式比Vue2好在哪里? 效率更高了,Vue2中假设监听某个对象,该对象中有一万个属性,他要循

    2024年02月11日
    浏览(38)
  • uniapp引入全局js,vue2/vue3不同方式引入

    Hi I’m Shendi uniapp引入全局js,vue2/vue3不同方式引入 最近写小程序,个人开发,选用了 uni-app 进行开发 选用的 vue3 版本 因为我用的 vue3 版本,在这里踩了没学过vue3的坑,用vue2引入全局js的方式使用,导致undefined… Vue2 版引入全局js的方法如下 将js放到项目内,一般放到自建的

    2024年02月03日
    浏览(30)
  • 手写Vue3响应式数据原理

    我们想要对一个对象数据进行处理,从而实现更改dom。但如何更改对一个对象数据进行更改呢? vue2 的双向数据绑定是利⽤ES5 的⼀个 API ,Object.defineProperty()对数据进⾏劫持 结合 发布订阅模式的⽅式来实现的。 vue3 中使⽤了 ES6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀

    2024年02月11日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包