在对象中增加或者删除属性的时候,数据的响应式原理是不奏效的,因为vue2是用的Object.definedProperty
方法进行数据劫持。
因此在进行添加元素的时候,应该用$set
来进行添加属性。使用$remove
进行删除属性。
对于数组,因为数组也是一对象,但我们在使用数组的api进行操作数组(添加元素或者是删除元素)的时候,视图是有更新的。
这个的原因是为什么呢?
原本上,数组的一些方法比如push,pop是不会触发getter/setter的。
不会触发的原因是因为这是Array原型上的方法,并没有在Array本身上面。
vue可以使用这些方法的原因是因为vue重写了这些方法。就可以使用 push.pop.shift,unshift,splice,sort,reserve
操作数组,并且进行响应式。
实现的思路:大体上就是说,是使用了拦截器覆盖了Array.prototype上的方法,在执行原型上的方法之外去做数据的响应式。
将数组的原型存到对象arrayMethods中
找到Array上能够改变数组自身的7个方法 push, pop, shift,unshift, splice, sort, reverse
将这7个方法进行响应式处理
处理完成后,用它们把arrayMethods中对应的方法覆盖掉
将需要进行响应式处理的数组arr的__proto__指向arrayMethods,如果浏览器不支持访问__proto__,则直接将响应式处理后的7个方法添加到数组arr上
如果要将一个数组完全实现响应式,需要遍历该数组,将数组中的数组使用该方法进行响应式处理,将对象使用walk方法进行响应式处理
// 获取Array的原型
const arrayProto = Array.prototype;
// 创建一个新对象,该新对象的原型指向Array的原型。
export const arrayMethods = Object.create(arrayProto);
[
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
.forEach(mentod => {
// 缓存原始方法
const original = arrayProto[method];
// 对新原型对象上的方法,做数据绑定
Object.defineProperty(arrayMethods, method, {
value: function mutator(...args) {
// 返回原始方法
return original.apply(this, args);
},
enumerable: false,
writable: true,
configurable: true
})
})
import { arrayMethods } from './array' // 处理好的Array原型对象
// __proto__是否可用
const hasProto = '__proto__' in {};
// 所有属性名,不论是否可枚举(与Object.keys的区别)
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);
export class Observe {
// 将value转为响应式
constructor (value) {
this.value = value;
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment;
augment(value, arrayMethods, arrayKeys);
} else {
this.walk(value); // Object的响应式处理,在其他文章中
}
}
}
/**
* __proto__可用的处理方式
* 将target对象的原型对象替换为src
*/
function protoAugment(target, src, keys) {
target.__proto__ = src;
}
/**
* __proto__不可用的处理方式
* 将src上面的所有属性都copy到target
*/
function copyAugment (target, src, keys) {
for (let i = 0, len = keys.length; i < len; i ++) {
const key = keys[i];
def(target, key, src[key]);
}
}
// Object.defineProperty()的封装
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
收集依赖:
function defineReactive(data, key, val) {
let childOb = observe(val);
let dep = new Dep(); // 存储依赖
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function () {
dep.depend();
if (childOb) childOb.dep.depend(); // 收集
return val;
},
set: function (newVal) {
if (val === newVal) return;
dep.notify();
val = newVal;
}
})
}
// 返回val的响应式对象
function observe(val, asRootData) {
if (!isObject(value)) return;
let ob;
// 避免重复侦测
if (hasOwn(value, '__ob__') && value.__ob__ instanceof observer) {
ob = value.__ob__;
} else {
ob = new Observe(value)
}
return ob;
}
但是针对
Let a = []
a[0] = 1
a.length = 0
下面这两种改变数组的方式是不会触发响应式的
可以使用$set
方式来进行添加改变数组。或者是使用$remove
来改变数组。