Vue2响应式原理分析(数据代理与数据劫持)

本文详细解释了Vue应用中如何通过Vue构造函数创建实例,以及数据代理、数据劫持的过程。重点介绍了数据代理如何通过观察者(observer)实现属性的响应式,并揭示了DOMListeners和DataBindings在数据变化与视图更新中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

综述:

  • 我们都知道,每个Vue的应用都是通过new一个Vue构造函数从而创造出来一个vm实例对象,el(elect)配置项为通过id选择器#root选择index页面中的根dom元素进行绑定,data配置项则为vue模板中用到的源数据。

  • Vue实例在创建时,会接收data对象,并遍历此对象所有的属性,并使用Object.defineProperty将属性全部转为getter/setter,以便追踪属性的变化(obs)。当用户在View层进行操作时,ViewModel能感知到变化并对Model层的数据进行更新,反之亦然。

数据代理:

  • 数据代理即为通过操作对象a来直接触碰对象b达到修改对象b的目的的操作。

  • 在Vue响应式原理中,数据代理则体现在了data配置项通过一系列操作(下文中会分析)赋到了vm实例的_data属性上,然后vm对_data做了代理,即通过Object.defineProperty为vm实例响应式地添加了_data中的属性,从而使Vue模板中不必使用this._data.name来使用,直接使用this.name即可。(Ps:凡是vm实例身上的属性,在Vue模板中均可直接使用,即插值语句{{name}}即可)

  • 而在某种意义上_data与data相同。

  

tips:
  • 一旦data中数据发生改变,那么页面中用到该数据的地方自动更新。

  • _data代理了data,vm代理了_data。

  • 通过defineProperty()方法为vm添加_data中有的属性,再提供setter/getter。

  • 疑点:data经加工后vm._data===data的原因,修改data属性能够重新渲染页面的原因。

  • 原数据data已经被obs劫持,只能通过obs访问。

  • _data里面的属性已经变成访问器属性。

  • _data直接赋值new Vue时传入的data,所以指向同一个地址,二者无区别。

数据劫持:

        上文中提到某种意义上data===_data,这里将介绍数据代理图示中黄色线中所做的操作。

  • 首先data代表vm实例配置项中的源数据,其需要经过加工后,赋给vm._data,所以说二者相同。

  •  至于是如何加工的,下图中的代码做了模拟,用到了观察者对象。

  • 首先data模拟的是未经加工的源数据,将data传入了观察者对象(即obs)的构造函数作为参数(即obj)。传入的为对象data,为其地址。

 
tips:    

        在Java中,函数参数传递的是对象的引用(内存地址),而不是对象本身。当一个对象作为参数传递给一个函数时,实际上是将对象的引用(内存地址)传递给了函数,函数中对该对象的操作会影响到原始对象。因此,在函数内部对传递的对象进行修改时,会影响到原始对象的状态。

       由此,下文中obs,data,._data操作的为同一块内存,故data即为._data

  • 在观察者对象(即obs)中所作的操作就是把obj(即传入的data源数据)的所有的属性名提取出来,为这个obs通过Object.defineProperty的形式响应式地(即getter、setter)添加data中的所有属性(若有深层对象则递归添加)。

  • 现在这个obs身上有了data中的所有属性及其响应式。

  • 之所以采用obs,是为了避免递归死循环。

  • 在下图代码中,然后就是将这个构造好的obs连续赋给data及vm._data(实际中为引用传参)

  • 在此时,若._data属性值修改,很明显将触发对应属性匹配的setter,而这个setter具有执行解析模板、生成虚拟dom、新旧dom的diff算法对比、生成新页面的功能逻辑。

  • 而数据劫持就是体现在这个setter的实际操作中。

  • 在某种意义上,观察者obs相当于一个辅助对象。

  • 在下图中可以看到,._data身上属性的setter和getter,其对源data通过obs套了一层代理,系统命名为reactiveSetter和reactiveGetter,因为其中封装了更新页面的逻辑操作,所以不同。

  • 而且可以看到,._data实际上即为Observer对象,即._data中属性改动将触发Setter渲染页面。

  • 当._data中任意一个reactiveSetter被触发后, 都会使Vue重新解析页面,所以说属性修改后页面重新解析的关键在于Setter。

Vue响应式整体流程: 

  • Vue框架是一个MVVM框架。

(详见我的上一篇博文)MVVM模型

  •   VM即为我们new出来的实例对象,M即为Model(data源数据),V即为View视图(Vue模板template) 。

  • 简而言之,就是在Vue中,ViewModel就是Vue实例。Vue实例在创建时,会接收data对象,并遍历此对象所有的属性,并使用Object.defineProperty将属性全部转为getter/setter,以便追踪属性的变化(obs)。当用户在View层进行操作时,ViewModel能感知到变化并对Model层的数据进行更新,反之亦然。

DOM Listeners:
  • DOM Listeners是对DOM结构进行监听,若发生数据改动则通过DOM listenersDOM监听器将数据传到Model中。

  • 即当vm身上的根属性发生改动(即DOM结构发生改变)后,由于vm对其属性._data做了一层数据代理,其对根属性的改动将通过vm身上的该根属性对应的setter来实现对._data中的对应属性的改动,而._data中属性改动又将通过obs传导到Model中。

Data Bindings:
  • Data Bindings是把Model的数据通过Data Bindings数据绑定送到Vue的模板实例对象上。

  • 即data源数据通过obs传导到._data中,一般情况下vm实例又对._data进行了代理,从而通过vm实例对._data的代理实现了将Model(data)中传来的数据绑定送到Vue模板实例对象的操作。

  • 又因为使用Object.defineProperty,故实现的是使用getter和setter的数据双重绑定操作。

  •  改属性页面变化原理大致如下图所示:

(其中Observer的setter与getter即为上文中提到的reactiveGetter/reactiveSetter。)

 

  •  以下代码为简单的响应式模拟(Object.defineProperty应用):
//源数据
let person = {
	name:'张三',
	age:18
}
//模拟Vue2中实现响应式
let p = {}
Object.defineProperty(p,'name',{
	configurable:true,
	get(){ //有人读取name时调用
		return person.name
	},
	set(value){ //有人修改name时调用
		console.log('有人修改了name属性,我发现了,我要去更新界面!')
		person.name = value
	}
})
Object.defineProperty(p,'age',{
	get(){ //有人读取age时调用
		return person.age
	},
	set(value){ //有人修改age时调用
		console.log('有人修改了age属性,我发现了,我要去更新界面!')
		person.age = value
	}
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值