从0到1实现一个响应式函数

响应式是啥

可以自动响应数据变量的代码机制,我们就称之为是响应式的。

 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里面使用的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值