# 响应式
Vue 是采用数据劫持结合观察者(发布者-订阅者)模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter, 在数据变动时发布消息给订阅者(watcher),触发相应的监听回调来更新DOM
# 数据代理
# 数组响应式处理
数组某些特殊的查找情况,会出现查找不到的结果
查找响应式数据本身
const obj = {} const proxy = reactive([obj]) console.log(proxy.includs(proxy[0])) //false
查找原始数据
const obj = {} const proxy = reactive([obj]) console.log(proxy.includs(obj)) // false
getter
createGetter() {
// ...
// 处理数组类型的 getter
const targetIsArray = shared.isArray(target);
if (!isReadonly && targetIsArray && shared.hasOwn(arrayInstrumentations, key)) {
// 重写/增强数组的方法:
// - 查找方法:includes、indexOf、lastIndexOf
// - 修改原数组长度的方法:push、pop、unshift、shift、splice
return Reflect.get(arrayInstrumentations, key, receiver);
}
// ...
}
// 数组捕获器,包含 3个查找和5个影响长度的方法
const arrayInstrumentations = createArrayInstrumentations();
function createArrayInstrumentations() {
const instrumentations = {};
// 1.修改数组查找的方法,处理查找失败的问题
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
instrumentations[key] = function (...args) {
// 外部调用查找方法,此时的上下文 this 是代理后的 数组
// 使用查找的方法时,需要到原始数据中去寻找,所以使用 toRaw 还原响应式数据
const arr = toRaw(this);
for (let i = 0, l = this.length; i < l; i++) {
track(arr, "get", i + '');
}
// 执行数组方法的时候,先尝试使用原始的参数(参数可能也是响应式数组)
const res = arr[key](...args);
if (res === -1 || res === false) {
// 如果方法执行无结果,则使用响应式的参数
return arr[key](...args.map(toRaw));
}
else {
return res;
}
};
});
// 2.改写5个会修改数组长度的方法
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
instrumentations[key] = function (...args) {
// 这些方法会隐式的改变数组的长度,导致 length 副作用被提前收集
// 使用 pauseTracking 先暂停依赖收集,待方法执行完毕再恢复收集
pauseTracking();
const res = toRaw(this)[key].apply(this, args);
resetTracking();
return res;
};
});
return instrumentations;
}
setter
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// ...
// 通过 "当前操作的下标是否 < 数组长度" 判断是 "修改 or 新增"类型
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 设置对应值
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) {
if (!hadKey) {
// 目标对象不存在对应的 key,则为新增操作,会改变 length,需要触发 length 副作用
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 目标对象存在对应的值,则为修改操作,不需要触发length 副作用
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
// 返回修改结果
return result
}
}
# 依赖收集:track
# 副作用函数
# 数据结构
const depsMap = new WeakMap()
const dep = new Map()
const fn = new Set()
{
// depsMap
target1: {
// dep
key1: [
// effect
fn1,
fn2
],
key2: [fn3, fn4]
},
target2: {
key: [fn5, fn6]
}
}