从vue通过Object.defineProperty方法劫持数据可知,vue是监听不到对象属性的增加或删除的,因为这些属性没有被劫持。想要监听数据变化,我们就要将其转换为响应式,这时候可以用this.$set(data, key, value)。
这个$set里面做了什么?
一、 给一个对象新增属性
我们将源码逻辑简化:
set(target, key, val) {
...
const ob = target._ob_; // 获取对象的_ob_属性,它为劫持数据时创建的observer实例
...
defineReactive(obj, key, val); // 通过definedReactive方法劫持属性,将其转换为响应式
ob.dep.notify(); // 手动让发布者发送消息,通知订阅者进行更新
return val;
}
二、给一个数组的一个位置添加元素
set(target, key, val) {
...
if (Array.isArray(target)) { // 判断是数组
target.length = Math.max(target.length, key); // 更新数组长度
target.splice(key, 1, val); // 通过splice方法向数组指定位置插入元素
return val;
}
...
}
或许有人会问了,splice方法会改变原始数组,但是不改变数组在堆内存中的地址,为毛可以通过splice插入元素的方式触发订阅者更新?其实vue内部将数组原型中的方法篡改了,主要做了什么呢?
我们先列举出会改变原始数组的原型方法
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
源码中依次遍历这些原型方法,改写其逻辑。主要是:首先获取原型方法执行后的返回值,然后通过数组的“ob”属性发出消息通知订阅者更新,然后将值返回。当使用splice,push,unshift这三个方法时,会额外对新增的元素做一次劫持。
由于原型方法已经被篡改了,只要使用这些方法修改数组,就会触发响应。
注:
1、直接修改数组某个索引的元素触发响应,arr[index] = newVal;
2、修改数组的长度length不会触发更新, arr.length = newVal;
相比大家也明白的原因,不在阐述。有问题请指出,谢谢!
网友评论