如何使用Object.defineProperty对数组的push等方法进行响应式监听,以及实现对象的深层嵌套监听

通常我们可以对某个对象上通过Object.defineProperty定义的属性进行数据更改读取的监听。但是对数组或是对象等引用类型数据,如果只是改变其中索引的值或者某个键的值是无法监听到的(因为监听的就是其内存地址的指向是否改变),这个vue2中也有明确表示。当然除了以上,调用数组的push,splice等方法也无法监听到数组的更改,只有重新对数组/对象这种引用类型赋新值,改变其内存地址指向才会被监听到。

那么如何达到数组调用push等方法也能达到监听的效果呢?

首先我们要知道数组本身上是没有push,splice等方法的,数组的构造函数是Array类,array的原型对象上才有push等方法,根据原型链查找,数组在调用push方法时,实际上是调用的其构造函数Array上原型对象中的push等方法。
我们虽然不能重新封装Array构造函数的原型对象中的push方法,但是我们可以使需要监听的数组重新指向一个新的原型对象,然后我们自己封装一些push等数组方法在这个新的原型对象中。当需要监听的数组调用push等方法时,根据原型链的指向,会调用我们新的这个原型对象中自己封装的push等方法。然后我们在自己封装的push等方法中 调用Array构造函数原型对象上的push等方法,并同时改变其内部的this指向 调用的数组本身(这一步是关键),这样就等同于数组直接调用Array构造函数原型对象上的push等方法。只不过我们中间封装了一层自己定义的push方法,然后我们手动来调用Array的push等方法,而不是数组来调用,这样就需要在调用Array原型对象中的push等方法时将其内部this指向数组本身,以此来模拟数组本身调用Array原型对象中的push等方法。然后我们就能在自己定义封装的push等方法中进行一些监听操作,当数组每次调用push等方法时都是调用的我们封装的这些方法,然后我们再帮其调用Array原型对象中的这些方法。

所以综上所述就是一句话,我们封装一层中间件,让数组调用push等方法时调用我们中间件函数,我们在中间件函数中帮数组调用Array原型对象中的push方法,以此来模拟数组本身调用Array原型对象上的方法,我们即可在中间件函数中进行一些监听处理操作(类似于中介)

概念讲了这么多,为了更好理解讲的比较细有很多废话。大家理解

话不多说下面直接上代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		
		
		<script>
			var obj={

			}
			
			var olist =[]

			//我们对obj对象中的属性进行响应式的监听,在obj中创建了一个具有响应式的list属性
			//我们调用obj.list赋值时都可以被set方法监听到,但是注意我们obj.list调用xxx方法时,实际上的步骤是先被get监听到,返回的olist。也就是实际上obj.list调用xxx方法时,其实是olist在调用xxx方法,
			Object.defineProperty(obj,'list',{
				get(){
					return olist 
				},
				set(newValue){
					olist =newValue
				}
			})
			
			
			
			
			//我们要对哪个数组的push等其他方法做监听,就要修改这个数组的方法的this指向,由于数组本身没有这些方法,只有Array原型对象上存在。但是由于原型链的关系。普通数组也能使用Array上的方法
			//所以数组在调用push等方法时是直接调用的Array上的psuh等方法,这我们没办法直接做到监听,但是我们可以中间加一层,例如自己写一个push方法加到数组的原型对象上,然后内部调用Array的push方法,调用的同时将其内部的this指向改变。不就等同于数组直接调用Array的push方法吗。这样我们中间自己封装了一层psuh方法就可以在内部进行监听
			
		
			
			
			let ArrayProto = Array.prototype; //得到Array的原型对象
			let arrayMethod = Object.create(ArrayProto);//克隆一份作为监听数组的原型对象,当然我们也可以自己定义一个新的对象,然后给新的对象添加上自己封装的push等方法,但是我这里为了更好的模拟Array的原型对象,就直接克隆一份罗

			[
			  'push',
			  'pop',
			  'shift',
			  'unshift',
			  'splice',
			  'sort',
			  'reverse'
			].forEach((method) => {
			  let original = ArrayProto[method];//获取Array原型对象上的push等方法
			
			 //重新封装一下push方法
			  arrayMethod[method] = function mutator(arguments) {
				  
				  //....做一些监听处理
			    console.log("监听到数组的变化啦!");
				original.call(this, arguments);//依然调用Array原型上的push等方法,改变其内部this指向数组,就等同于数组调用的Array中的push方法
			  };
			   olist .__proto__=arrayMethod	//将需要监听的olist数组的__proto__指向克隆的原型对象。我们为什么不对通过Object.defineProperty定义的属性进行监听呢,因为我们访问Object.defineProperty定义的list属性时最终访问的就是olist,我们调用Object.defineProperty定义的list属性的push方法,最终其实就是访问到olist然后调用olist的push方法。这一点要弄清楚。


我们调用obj.list赋值时都可以被set方法监听到,但是注意我们obj.list调用xxx方法时,实际上的步骤是先被get监听到,返回的olist。也就是实际上obj.list调用xxx方法时,其实是olist在调用xxx方法,所以其实需要监听的是olist的push等方法的调用
			});
			
			
			
		</script>
	</body>
</html>

2.实现对象深层嵌套监听


	var obj={
				addr:'湖南',
				goods:{
					fruits:[
						{
							name:'🍉',
							price:12
						},
						{
							name:'🍌',
							price:11
						}
					]
				},
				traffic:{
					car:[
						{
							name:'🚗'
						},
						{
							name:'🚓'
						}
					]
				}
			}
			
	function observe(data,rootAttr,isRoot){
			for(let key in data){
				let val=data[key]
				Object.defineProperty(data,key,{
					set(newval){
							if(isRoot){
								rootAttr=key
							}
							console.log(`监听到${rootAttr}设置`)
						
						val=newval
					},
					get(){
						console.log('监听到读取')
						return val
					}
				})
				if(Object.prototype.toString.call(val)=='[object Object]'){
					rootAttr=key
					observe(val,rootAttr,false)
				}
			}
		}
		observe(obj,undefined,true)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值