我们先来看一下响应式意味着什么?
♀️ 我们来看一段代码:⬇️
let m = 20;
console.log(m);
console.log(m*2);
m=40;
⬆️ 上面的这样一种可以自动响应数据变量的代码机制,我们称之为是响应式的。
对象的响应式:⬇️
☝️ 首先,执行的代码中可能不止一行代码,所以我们可以将这些代码放到一个函数中:
但是,有一个问题:
❓❓在开发中我们是有很多的函数的,我们如何区分一个函数需要响应式,还是不需要响应式呢?
function foo() {
//依赖于obj.name
let newName = obj.name;
console.log(obj.name);
}
function bar() {
const result = 20 + 20;
console.log(result);
console.log('hello world');
}
将依赖于另一个数据的代码片段,放进一个函数中,
当数据发生变化时,依赖于该数据的函数自动执行。
但是,我们怎么区分呢?
let obj = {
name: '张三',
age: 32,
};
const reactiveFns = [];
function watchFn(fn) {
reactiveFns.push(fn);
fn();
}
reactiveFns.forEach((fn) => {
fn();
});
console.log('---name发现变化----');
obj.name = '李四';
watchFn(function () {
let newName = obj.name;
console.log('foo:', obj.name);
console.log('bar:', obj.age);
});
watchFn(function () {
console.log('bar:', obj.age);
console.log('bar:', obj.name + '!!');
});
class Depend {
constructor() {
this.reactiveFns = [];
}
addDepend(fn) {
if (fn) {
this.reactiveFns.push(fn);
}
}
notify() { //通知函数重新执行
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
let obj = {
name: '张三',
age: 32,
};
// 设置一个专门执行响应式函数的一个函数
const dep = new Depend();
function watchFn(fn) {
dep.addDepend(fn);
fn();
}
// console.log('---name发现变化----');
watchFn(function foo() {
let newName = obj.name;
console.log('foo:', obj.name);
console.log('foo:', obj.age);
console.log('foo:function');
});
watchFn(function bar() {
console.log('bar:', obj.age + 20);
console.log('bar:', obj.name + '!!');
console.log('bar:function');
});
console.log('---修改obj的属性值---');
console.log('-----name发生改变-----');
obj.name = '李四';
dep.notify(); //每次都是手动触发 修改通知
console.log('-----age发生改变-----');
obj.age = 42;
dep.notify();
console.log('-----name发生改变-----');
obj.name = '王五';
// 不通知dep.notify(); 就不会执行
那么我们接下来就可以通过之前学习的方式来监听对象的变量:
方式一:通过 Object.defineProperty 的方式(vue2采用的方式);
方式二:通过new Proxy的方式(vue3采用的方式);
// 方式一:Object.defineProperty
Object.keys(obj).forEach((key) => {
let value = obj[key];
Object.defineProperty(obj, key, {
set: function (newValue) {
value = newValue;
dep.notify();
},
get: function () {
return value;
},
});
});
const objProxy = new Proxy(obj, {
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver);
dep.notify();
},
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
});
改变一个对象的属性值,整个函数都会重新执行,实际上我们需要执行的只是依赖于发生改变的属性的函数。
比如:改变obj的name属性,只需要通知只是依赖于name的函数重新执行
但是目前的问题:
对obj来说,对应的是同一个dep对象;
只有一个dep对象,对于name属性依赖的函数和age属性依赖的函数都在同一个dep对象的属性reactiveFns中;
属性的依赖都在reactiveFns数组中,无法区分。
解决方式:⬇️ 通过 map
我们目前是创建了一个Depend对象,用来管理对于name变化需要监听的响应函数:
但是实际开发中我们会有不同的对象,另外会有不同的属性需要管理;
我们如何可以使用一种数据结构来管理不同对象的不同依赖关系呢?
在前面我们刚刚学习过WeakMap,并且在学习WeakMap的时候我讲到了后面通过WeakMap如何管理这种响应式的数据依赖:
分析:比如:obj.name 使用就放进 obj的map中的name对应的dep中
- dep对象数据结构的管理(最难理解)
- 每一个对象的每一个属性都会对应一个dep对象
- 每一个对象的多个属性的dep对象是存放在一个map对象中
- 多个对象的map对象,会被存放到一个objMap的对象中
- 依赖收集:当执行get函数,自动添加fn函数
写一个getDepends()专门管理
// 自动通过obj的key自动收集对象的Depend对象
const objMap = new WeakMap();
function getDepends(obj, key) {
//1.根据对象的obj,找到对应的Depend对象
let map = objMap.get(obj);
if (!map) {
map = new Map();
objMap.set(obj, map);
}
// 2.根据不同的key,找到对应的depend对象
let dep = map.get(key);
if (!dep) {
dep = new Depend();
map.set(key, dep);
}
return dep;
}
我们之前收集依赖的地方是在 watchFn 中:
但是这种收集依赖的方式我们根本不知道是哪一个key的哪一个depend需要收集依赖;
你只能针对一个单独的depend对象来添加你的依赖对象;
那么正确的应该是在哪里收集呢?
应该在我们调用了Proxy的get捕获器时
因为如果一个函数中使用了某个对象的key,那么它应该被收集依赖;
// 设置一个专门执行响应式函数的一个函数
let reactiveFn = null;
function watchFn(fn) {
reactiveFn = fn;
fn();
reactiveFn = null;
}
// 方式一:Object.defineProperty
Object.keys(obj).forEach((key) => {
let value = obj[key];
Object.defineProperty(obj, key, {
set: function (newValue) {
value = newValue;
const dep = getDepends(obj, key);
dep.notify();
},
get: function () {
// console.log(obj, key);
// 精准的找到obj key的dep对象
const dep = getDepends(obj, key);
dep.addDepend(reactiveFn);
return value;
},
});
});
//方式二:通过new Proxy的方式(vue3采用的方式);
const objProxy = new Proxy(obj, {
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver);
const dep = getDepends(target, key);
dep.notify();
},
get: function (target, key, receiver) {
const dep = getDepends(target, key);
dep.addDepend(reactiveFn);
return Reflect.get(target, key, receiver);
},
});
⚠️ 但是这里有两个问题:
❓❓问题一:如果函数中有用到两次key,比如name,那么这个函数会被收集两次;
❓❓问题二:我们并不希望将添加reactiveFn放到get中,以为它是属于Dep的行为;
所以我们需要对Depend类进行重构:
✅ ✅ 解决问题一的方法:不使用数组,而是使用Set;
✅ ✅ 解决问题二的方法:添加一个新的方法,用于收集依赖;
let obj = {
name: '张三',
age: 32,
address: '北京',
};
Object.keys(obj).forEach((key) ...
......
watchFn(function () {
console.log('----1----');
console.log(obj.name);
console.log(obj.age);
});
watchFn(function () {
console.log('----2----');
console.log(obj.age);
console.log(obj.age);
});
watchFn(function () {
console.log('----3----');
console.log(obj.name);
console.log(obj.address);
});
// 改变age
obj.age = 26;
问题: console.log(obj.age);执行了两次
解决:⬇️
class Depend {
constructor() {
this.reactiveFns = new Set(); //Set 中的元素只会出现一次
}
addDepend(fn) {
if (fn) {
// this.reactiveFns.push(fn);
this.reactiveFns.add(fn);
}
}
depend() {
if (reactiveFn) {
this.reactiveFns.add(reactiveFn);
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
封装Object
function reactive(obj) {
// 方式一:Object.defineProperty
Object.keys(obj).forEach((key) => {
let value = obj[key];
Object.defineProperty(obj, key, {
set: function (newValue) {
value = newValue;
const dep = getDepends(obj, key);
dep.notify();
},
get: function () {
// console.log(obj, key);
// 精准的找到obj key的dep对象
const dep = getDepends(obj, key);
dep.addDepend(reactiveFn);
return value;
},
});
});
return obj;
}
// 方式二: new proxy()
function reactive(obj) {
const objProxy = new Proxy(obj, {
set: function (target, key, newValue, receiver) {
Reflect.set(target, key, newValue, receiver);
const dep = getDepends(target, key);
dep.notify();
},
get: function (target, key, receiver) {
const dep = getDepends(target, key);
dep.addDepend(reactiveFn);
return Reflect.get(target, key, receiver);
},
});
return objProxy;
}
// ----业务代码----
let obj1 = reactive({
name: '张三',
age: 32,
address: '北京',
});
watchFn(function () {
console.log('----obj1----');
console.log(obj1.name);
console.log(obj1.age);
console.log(obj1.address);
});
const user = reactive({
nikiname: 'park',
level: 11,
});
watchFn(function () {
console.log('----user----');
console.log(user.nikiname);
console.log(user.level);
});
class Depend {
constructor() {
this.reactiveFns = new Set();
}
addDepend(fn) {
if (fn) {
// this.reactiveFns.push(fn);
this.reactiveFns.add(fn);
}
}
depend() {
if (reactiveFn) {
this.reactiveFns.add(reactiveFn);
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
// 设置一个专门执行响应式函数的一个函数
let reactiveFn = null;
function watchFn(fn) {
reactiveFn = fn;
fn();
reactiveFn = null;
}
// 自动通过obj的key自动收集对象的Depend对象
const objMap = new WeakMap();
function getDepends(obj, key) {
//1.根据对象的obj,找到对应的Depend对象
let map = objMap.get(obj);
if (!map) {
map = new Map();
objMap.set(obj, map);
}
// 2.根据不同的key,找到对应的depend对象
let dep = map.get(key);
if (!dep) {
dep = new Depend();
map.set(key, dep);
}
return dep;
}
// 方式一:Object.defineProperty
function reactive(obj) {
Object.keys(obj).forEach((key) => {
let value = obj[key];
Object.defineProperty(obj, key, {
set: function (newValue) {
value = newValue;
const dep = getDepends(obj, key);
dep.notify();
},
get: function () {
// console.log(obj, key);
// 精准的找到obj key的dep对象
const dep = getDepends(obj, key);
//dep.addDepend(reactiveFn);
dep.depend();
return value;
},
});
});
return obj;
}
// // 方式二: new proxy()
// function reactive(obj) {
// const objProxy = new Proxy(obj, {
// set: function (target, key, newValue, receiver) {
// Reflect.set(target, key, newValue, receiver);
// const dep = getDepends(target, key);
// dep.notify();
// },
// get: function (target, key, receiver) {
// const dep = getDepends(target, key);
// dep.depend();
// return Reflect.get(target, key, receiver);
// },
// });
//
// return objProxy;
// }
// ----业务代码----
let obj1 = reactive({
name: '张三',
age: 32,
address: '北京',
});
watchFn(function () {
console.log('----obj1----');
console.log(obj1.name);
console.log(obj1.age);
console.log(obj1.address);
});
const user = reactive({
nikiname: 'park',
level: 11,
});
watchFn(function () {
console.log('----user----');
console.log(user.nikiname);
console.log(user.level);
});
// 改变
user.level = 12;