这是我参与新手入门的第三篇文章
知识点
本篇文章主教你一步一步的手写Vue3.x的响应式原理,文中涉及的知识点如下:
- defineProperty 和 Proxy 数据代理
- ES6 之 WeakMap
- 设计模式 | 发布-订阅
一 、defineProperty 和 Proxy 数据代理
相同点:
(1)两者都能对数据进行挟持
不同点
(1)defineProperty根据key值来进行监听,Proxy则根据整个对象进行监听
(2)defineProperty在原对象上修改操作污染原对象
(3)Proxy 则是会返回一个新的代理对象,操作都再新对象当中,原对象并不会被污染
1. defineProperty
一个简易的defineProperty数据挟持
const watch = (target, property, callback) => {
// 存储当前值
let _value = target[property];
// 使用defineProperty对数据进行监听
Object.defineProperty(target, property, {
get() {
console.log('正在获取对象的:' + property + '属性值')
return _value;
},
set(newVal) {
// 记录旧值
const lastVal = _value;
// 设置新值
_value = newVal;
// 回调返回新、旧值
callback && callback(newVal, lastVal);
}
});
}
// 创建一个测试对象
let test = {
count: 1
}
// 执行监听
watch(test, 'count', (newVal, lastVal) => {
console.log("test对象中count发生变化:", newVal, lastVal);
})
test.count += 1;
复制代码
控制台输出
2. Proxy
一个简易的Proxy数据挟持
// Proxy 直接传入一个对象,返回一个新对象(代理对象)
const watch = (target, callback) => {
return new Proxy(target, {
get(target, key) {
return target[key];
},
set(target, key, newVal) {
// 获取旧值
const lastVal = target[key];
// 设置新值
target[key] = newVal;
// 回调 - 返回新、旧值
callback(newVal, lastVal);
}
})
}
// 创建一个测试对象
let test = {
count: 1
}
let newTest = watch(test, (newVal, lastVal) => {
console.log("test对象中count发生变化:", newVal, lastVal);
})
// 修改新对象中的count
newTest.count += 1;
复制代码
控制台输出:
二、ES6 WeakMap
1.Map
Map 是 ES6 新增的有序键值对集合。键值对的 key 和 value 都可以是任何类型的元素。
示例:
let map = new Map();
// 创建测试对象
let test_1 = {
func: () => {
console.log('对象的方法')
}
};
// 创建测试字符串变量
let test_2 = 'test_2';
// 设置 key - value
map.set(test_1, 1);
map.set(test_2, 2);
// 以对象为key来访问数据
let value_1 = map.get(test_1); // -> value_1 = 1
let value_2 = map.get(test_2); // -> value_2 = 2
复制代码
2. WeakMap
WeakMap 相对于普通的 Map,也是键值对集合,只不过 WeakMap 的 key 只能是非空对象(non-null object)。WeakMap 对它的 key 仅保持弱引用,也就是说它不阻止垃圾回收器回收它所引用的 key。WeakMap 最大的好处是可以避免内存泄漏。一个仅被 WeakMap 作为 key 而引用的对象,会被垃圾回收器回收掉。
示例:
let map = new WeakMap();
// 创建测试对象
let test_1 = {
func: () => {
console.log('对象的方法')
}
};
// 创建测试字符串变量, 报异常
// let test_2 = 'tt'; // error -> Invalid value used as weak map keyat WeakMap.set
// let test_2 = null;// error -> Invalid value used as weak map keyat WeakMap.set
let test_2 = {};
// 设置 key - value
map.set(test_1, 1);
map.set(test_2, 2);
// 以对象为key来访问数据
let value_1 = map.get(test_1); // -> value_1 = 1
let value_2 = map.get(test_2); // -> value_2 = 2
复制代码
三、设计模式 | 发布-订阅
参考本人写的小菜文: 设计模式 | “观察者”与“订阅发布”
四、手写实现Vue3.x响应式
let activeEffect;
// 订阅
class Dep {
constructor(arg) {
this.subscribers = new Set();
}
depend() {
// 添加订阅者
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify(...args) {
// 通知
this.subscribers.forEach(effect => effect(...args))
}
}
// 设置当前订阅者
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// 存储所有订阅实例
let depMap = new WeakMap();
// 获取当前对象的dep订阅,不存在则新建
function getDep(target) {
if (!depMap.get(target)) {
depMap.set(target, new Dep());
}
return depMap.get(target);
}
// 响应
function reactive(obj) {
return new Proxy(obj, reactiveHandler);
}
const reactiveHandler = {
get(target, key, reactive) {
let dep = getDep(target);
dep.depend();
// 使用Reflect 的原因
// Reflect 跟 Proxy 一样,只不过Reflect 不会报异常,一旦产生错误,则会返回false
return Reflect.get(target, key, reactive);
},
set(target, key, value, reactive) {
Reflect.set(target, key, value, reactive);
let dep = getDep(target);
dep.notify();
}
}
const op = reactive({
op: 1,
test: 2
})
watchEffect(() => {
console.log(op.test, 'op.test');
})
op.test = 5;
// -> 2 'op.test'
// -> 5 'op.test'
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END