响应式是啥
可以自动响应数据变量的代码机制,我们就称之为是响应式的。
let m = 2
console.log(m)
console.log(m ** 2)
响应式是当m被赋予新值的时候上面的两个打印重新执行 也就是当变量值变化的时候,与之变量有关的函数重新执行一遍,发生在页面上的表现是,当值变化时,展示也发生变化 js是面向对象语言,那么对象中的响应式是:现在obj是一个响应式对象
obj = {name: 'lss',age: 18,
}
obj.name = 'luyi' // obj的值变化
// 页面上执行
let newValue = obj.name
console.log(obj.name) // 更新为新值
响应式函数的设计
最简单的响应式函数
根据上面的理解,可以设计出以下最简单版本的响应式函数
obj = {name: 'lss',age: 18,
}
// 用于收集响应式函数
const watchFn = function(fn) {fn()
};
obj.name = 'luyi';
watchFn(() => {console.log('obj.name>>>>>', obj.name);
});
打印结果如下:
obj.name>>>>> luyi
封装收集响应式函数的函数
但是有一个问题,当响应式函数一多,就需要写很多个watchFn
函数,这样才可以一一响应,有什么办法呢?用数组来收集响应式函数,上面的可以改进变成如下:
obj = {name: 'lss',age: 18,
};
// 用于收集响应式函数
let reactiveFns = [];
const watchFn = function(fn) {reactiveFns.push(fn);
};
watchFn(() => {console.log('obj.name>>>>>', obj.name);
});
watchFn(() => {console.log('obj.age>>>>>', obj.age);
});
obj.name = "luyi"
reactiveFns.forEach(fn => {fn()
});
打印结果如下:
obj.name>>>>> luyi
obj.age>>>>> 18
封装收集响应式函数的类
现在不用写很多个watchFn
函数了,但是还有不便之处,就是需要我们手动让响应函数们执行,手动触发监听函数,还是不太好,进一步优化
// 用于收集响应式函数
class Depend {constructor() {this.reactiveFns = []}addDepend(fn) {if(fn) {this.reactiveFns.push(fn)}}notify() {this.reactiveFns.forEach(fn => fn())}
}
// 封装一个响应式的函数
const depend = new Depend()
function watchFn(fn) {depend.addDepend(fn)
}
obj = {name: 'lss',age: 18,
};
watchFn(() => {console.log('obj.name>>>>>', obj.name);
});
watchFn(() => {console.log('obj.age>>>>>', obj.age);
});
obj.name = 'luyi';
depend.notify();
输出结果和上面一样,你可能想这个不是和上面的响应方式一样吗?只是变成了类来管理响应函数和通知,不着急,慢慢看,类的作用还没体现出来
在类里面实现自动响应
1.存取属性符 + proxy
// 用于收集响应式函数
class Depend {constructor() {this.reactiveFns = []}addDepend(fn) {if(fn) {this.reactiveFns.push(fn)}}notify() {this.reactiveFns.forEach(fn => fn())}
}
// 封装一个响应式的函数
const depend = new Depend()
function watchFn(fn) {depend.addDepend(fn)
}
obj = {name: 'lss',age: 18,
};
const objProxy = new Proxy(obj, {get(target, key, receiver) {return Reflect.get(obj, key, receiver)},set(target, key, newValue, receiver ) {Reflect.set(obj, key, newValue, receiver)depend.notify() // 在此处实现自动响应,只要obj的属性有变化,那么就会执行响应函数}
})
watchFn(() => {console.log('obj.name>>>>>', obj.name);
});
watchFn(() => {console.log('obj.age>>>>>', obj.age);
});
objProxy.name = 'luyi'; // 使用代理对象对obj的属性或者方法进行操作
打印结果和上条一样 2. 存取属性符 + defineProperty方法
// 用于收集响应式函数
class Depend {constructor() {this.reactiveFns = []}addDepend(fn) {if(fn) {this.reactiveFns.push(fn)}}notify() {this.reactiveFns.forEach(fn => fn())}
}
// 封装一个响应式的函数
const depend = new Depend()
function watchFn(fn) {depend.addDepend(fn)
}
obj = {name: 'lss',age: 18,
};
// const objProxy = new Proxy(obj, {
// get(target, key, receiver) {
// return Reflect.get(obj, key, receiver)
// },
// set(target, key, newValue, receiver ) {
// Reflect.set(obj, key, newValue, receiver)
// depend.notify() // 在此处实现自动响应,只要obj的属性有变化,那么就会执行响应函数
// }
// })
// 以上替换成以下
function reactive(obj) {Object.keys(obj).forEach(key => {let value = obj[key]Object.defineProperty(obj, key, {get() {return value;},set(newVal) {value = newVal;depend.notify()}})})return obj;
}
const objProxy = reactive(obj)
watchFn(() => {console.log('obj.name>>>>>', obj.name);
});
watchFn(() => {console.log('obj.age>>>>>', obj.age);
});
objProxy.name = 'luyi'; // 使用对象的存取属性符和defineProperty方法,来收集依赖
打印结果和上面一样 现在利用类的存取描述符以及Proxy或者defineProperty实现了自动响应 但是还是不完善,现在我们只改变了obj的name属性,为什么与age属性有关的也打印了呢?说明我们没有正确收集响应函数,响应函数没有正确依赖,所以还需要完善; 怎么完善呢? 按理应该每个属性,都应该对应着自己的许多个响应函数,也就是一个属性,应该对应着一个reactiveFns,而一个类只有一个reactiveFns,所以每个属性都对应着一个上面的类,怎么实现一个属性对应一个类?这个就要用到es6里面的map数据结构,往下看,如何使用:
正确的收集依赖
1.proxy
// 用于收集响应式函数
class Depend {constructor() {this.reactiveFns = []}addDepend(fn) {if(fn) {this.reactiveFns.push(fn)}}notify() {this.reactiveFns.forEach(fn => fn())}
}
// 这个函数很重要,必须理解存取符,才能理解这一步
let activeReactiveFn = null;
function watchFn(fn) {activeReactiveFn = fn;fn(); // 在这个过程中收集响应式函数, 用到哪个属性,这个函数就是哪个属性的响应函数activeReactiveFn = null; // 收集完毕,清空activeReactiveFn
}
obj = {name: 'lss',age: 18,
};
debugger;
// 封装获取对应依赖的函数
const targetMap = new WeakMap()
function getDepend(target, key) {let map = targetMap.get(target)if(!map) {map = new Map()targetMap.set(target, map)}let depend = map.get(key)if(!depend) {depend = new Depend()map.set(key, depend)}return depend;
}
const objProxy = new Proxy(obj, {get(target, key, receiver) {const depend = getDepend(target, key);depend.addDepend(activeReactiveFn)return Reflect.get(obj, key, receiver)},set(target, key, newValue, receiver ) {Reflect.set(obj, key, newValue, receiver)const depend = getDepend(target, key);depend.notify() // 在此处实现自动响应,只要obj的属性有变化,那么就会执行响应函数}
})
// function reactive(obj) {
// Object.keys(obj).forEach(key => {
// let value = obj[key]
// Object.defineProperty(obj, key, {
// get() {
// return value;
// },
// set(newVal) {
// value = newVal;
// depend.notify()
// }
// })
// })
// return obj;
// }
// const objProxy = reactive(obj)
watchFn(() => {console.log('obj.name>>>>>', objProxy.name); // 不直接对obj进行操作,使用代理函数操作
});
watchFn(() => {console.log('obj.age>>>>>', objProxy.age);
});
objProxy.name = 'luyi'; // 使用对象的存取属性符和defineProperty方法,来收集依赖
2.defineProperty()
// 用于收集响应式函数
class Depend {constructor() {this.reactiveFns = []}addDepend(fn) {if(fn) {this.reactiveFns.push(fn)}}notify() {this.reactiveFns.forEach(fn => fn())}
}
// 这个函数很重要,必须理解存取符,才能理解这一步
let activeReactiveFn = null;
function watchFn(fn) {activeReactiveFn = fn;fn(); // 在这个过程中收集响应式函数, 用到哪个属性,这个函数就是哪个属性的响应函数activeReactiveFn = null; // 收集完毕,清空activeReactiveFn
}
obj = {name: 'lss',age: 18,
};
debugger;
// 封装获取对应依赖的函数
const targetMap = new WeakMap()
function getDepend(target, key) {let map = targetMap.get(target)if(!map) {map = new Map()targetMap.set(target, map)}let depend = map.get(key)if(!depend) {depend = new Depend()map.set(key, depend)}return depend;
}
// const objProxy = new Proxy(obj, {
// get(target, key, receiver) {
// const depend = getDepend(target, key);
// depend.addDepend(activeReactiveFn)
// return Reflect.get(obj, key, receiver)
// },
// set(target, key, newValue, receiver ) {
// Reflect.set(obj, key, newValue, receiver)
// const depend = getDepend(target, key);
// depend.notify() // 在此处实现自动响应,只要obj的属性有变化,那么就会执行响应函数
// }
// })
function reactive(obj) {Object.keys(obj).forEach(key => {let value = obj[key]Object.defineProperty(obj, key, {get() {const depend = getDepend(obj, key);depend.addDepend(activeReactiveFn)return value;},set(newVal) {value = newVal;const depend = getDepend(obj, key);depend.notify()}})})return obj;
}
const objProxy = reactive(obj)
watchFn(() => {console.log('obj.name>>>>>', objProxy.name); // 不直接对obj进行操作,使用代理函数操作
});
watchFn(() => {console.log('obj.age>>>>>', objProxy.age);
});
objProxy.name = 'luyi'; // 使用对象的存取属性符和defineProperty方法,来收集依赖
打印结果如下:
obj.name>>>>> lss # 添加监听时的执行结果
obj.age>>>>> 18 # 添加监听时的执行结果
obj.name>>>>> luyi # objProxy.name = 'luyi'之后的响应执行结果
现在成功实现自动响应及自动收集对应响应 但是还有不完美的地方,比如:监听多个一模一样的函数,这个时候reactiveFns内部就会存放多个内存地址不同但是作用相同的函数,这个时候就可以用set数据结构来进行优化:
watchFn(() => {console.log('obj.name>>>>>', objProxy.name); // 不直接对obj进行操作,使用代理函数操作
});
watchFn(() => {console.log('obj.name>>>>>', objProxy.name);
});
优化完善, 只用更改reactiveFns的数据结构属性即可,将Array变成Set
优化之后的响应式结构:
// 用于收集响应式函数
let set = new Set([]);
class Depend {constructor() {this.reactiveFns = set}addDepend(fn) {if(fn) {this.reactiveFns.add(fn)}}notify() {this.reactiveFns.forEach(fn => fn())}
}
// 这个函数很重要,必须理解存取符,才能理解这一步
let activeReactiveFn = null;
function watchFn(fn) {activeReactiveFn = fn;fn(); // 在这个过程中收集响应式函数, 用到哪个属性,这个函数就是哪个属性的响应函数activeReactiveFn = null; // 收集完毕,清空activeReactiveFn
}
obj = {name: 'lss',age: 18,
};
debugger;
// 封装获取对应依赖的函数
const targetMap = new WeakMap()
function getDepend(target, key) {let map = targetMap.get(target)if(!map) {map = new Map()targetMap.set(target, map)}let depend = map.get(key)if(!depend) {depend = new Depend()map.set(key, depend)}return depend;
}
// const objProxy = new Proxy(obj, {
// get(target, key, receiver) {
// const depend = getDepend(target, key);
// depend.addDepend(activeReactiveFn)
// return Reflect.get(obj, key, receiver)
// },
// set(target, key, newValue, receiver ) {
// Reflect.set(obj, key, newValue, receiver)
// const depend = getDepend(target, key);
// depend.notify() // 在此处实现自动响应,只要obj的属性有变化,那么就会执行响应函数
// }
// })
function reactive(obj) {Object.keys(obj).forEach(key => {let value = obj[key]Object.defineProperty(obj, key, {get() {const depend = getDepend(obj, key);depend.addDepend(activeReactiveFn)return value;},set(newVal) {value = newVal;const depend = getDepend(obj, key);depend.notify()}})})return obj;
}
const objProxy = reactive(obj)
logName = () => {console.log('obj.name>>>>>', objProxy.name);
}
watchFn(logName);
watchFn(logName);
objProxy.name = 'luyi'; // 使用对象的存取属性符和defineProperty方法,来收集依赖
打印结果如下:
obj.name>>>>> lss # 添加监听时的执行结果
obj.name>>>>> lss # 添加监听时的执行结果
obj.name>>>>> luyi # objProxy.name = 'luyi'之后的响应执行结果
总结
以上是两种响应式函数的封装方法,proxy的是vue3里面使用的,defineProperty是vue2里面使用的