响应性基础:Proxy
Proxy是ES6中新添加的特性,用于给一个对象创建代理,用来拦截对象的操作。这样可以添加额外的业务逻辑或对原对象进行保护。
拦截方法参考:


示例:
let obj = {
a: 1,
b: 2,
};
let objProxy = new Proxy(obj, {
get(target, key, receiver) {
console.log(`访问了 ${target} 中的 ${key},值为 ${target[key]}`);
// return target[key];
// return 10;
return Reflect.get(target, key, receiver); // 调用Reflect.get()直接返回原对象的属性值
},
set(target, key, value, receiver) {
console.log(`修改了 ${target} 中的 ${key},值为 ${value}`);
// target[key] = value;
return Reflect.set(target, key, value, receiver); // 如果不调用Reflect.set()则原对象的值不会发生变化,可用来验证修改是否合法,如果合法返回Reflect.get(),否则提示错误
},
});
// console.log(objProxy.a);
objProxy.a = 5;
// 访问 proxy
console.log(objProxy.a);
// 访问原始对象
console.log(obj.a);
实现一个响应性示例(简化版):
目标:在修改对象的属性后,自动更新原对象的属性。
基础原理:
let obj = {
a: 1,
b: 2,
};
let reactiveObj = new Proxy(obj, {
set(target, key, value, receiver) {
let result = Reflect.set(target, key, value, receiver);
sum();
return result;
},
});
function sum() {
console.log(reactiveObj.a + reactiveObj.b);
}
sum();
setTimeout(() => {
reactiveObj.a = 5;
}, 1000);
setTimeout(() => {
reactiveObj.b = 10;
}, 2000);
封装一部分后:
const observers = new WeakMap();
let currentObserver = null;
function observe(fn) {
currentObserver = fn;
fn();
currentObserver = null;
}
let obj = {
a: 1,
b: 2,
};
// observe
let reactiveObj = new Proxy(obj, {
get(target, key, receiver) {
if (currentObserver) {
let targetObserver = observers.get(target);
if (targetObserver.has(key)) {
targetObserver.get(key).add(currentObserver);
} else {
targetObserver.set(key, new Set([currentObserver]));
}
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
let observersForkey = observers.get(target).get(key);
let result = Reflect.set(target, key, value, receiver);
observersForkey.forEach((fn) => fn());
return result;
},
});
observers.set(obj, new Map());
function sum() {
console.log(reactiveObj.a + reactiveObj.b);
}
observe(sum);
setTimeout(() => {
reactiveObj.a = 5;
}, 1000);
setTimeout(() => {
reactiveObj.b = 10;
}, 2000);
最后完整封装:
const observers = new WeakMap();
let currentObserver = null;
function observe(fn) {
currentObserver = fn;
fn();
currentObserver = null;
}
function reactive(obj) {
observers.set(obj, new Map());
return new Proxy(obj, {
get(target, key, receiver) {
registerObserver(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
execute(target, key);
return result;
},
});
}
function registerObserver(target, key) {
if (currentObserver) {
let targetObserver = observers.get(target);
if (targetObserver.has(key)) {
targetObserver.get(key).add(currentObserver);
} else {
targetObserver.set(key, new Set([currentObserver]));
}
}
}
function execute(target, key) {
let observersForkey = observers.get(target).get(key);
observersForkey.forEach((fn) => fn());
}
let obj = {
a: 1,
b: 2,
};
let reactiveObj = reactive(obj);
function sum() {
console.log(reactiveObj.a + reactiveObj.b);
}
function sub() {
console.log(reactiveObj.a - reactiveObj.b);
}
// 类似于调用 computed()
observe(sum);
observe(sub);
setTimeout(() => {
reactiveObj.a = 5;
}, 1000);
setTimeout(() => {
reactiveObj.b = 10;
}, 2000);
Virtual DOM:Vue 渲染机制的核心
因为使用Vue编写的程序,经常因为响应性的变化而重新渲染视图,如果直接操作真实的DOM,会是比较耗时的操作。这是因为需要找到某个节点,并将其换为新的节点,这样会触发浏览器的重新布局和重绘流程,对性能会有影响。
VirtualDOM的结构:

Vue有渲染工具,会将虚拟的DOM转换为真实的DOM,在节点变化后,会根据算法调整节点顺序或更换节点。这种比对算法Vue中称为Patch,其他框架有称为diff或reconciliation。
对于静态的DOM元素,Vue会保存这些节点,后续使用时会直接使用之前的节点;
对于响应性的节点,Vue会对包含这些节点的父节点维护一个记录,包含响应性数据的节点在更新时直接更新记录中的节点。