通常我们可以对某个对象上通过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)